简体   繁体   English

实体框架6和工作单元......何时何地?它就像ado.net中的交易一样吗?

[英]Entity Framework 6 and Unit Of Work… Where, When? Is it like transactions in ado.net?

Creating a new MVC project and like the idea of repositories in the data layer, so i have implemented them. 创建一个新的MVC项目,并喜欢数据层中的存储库的想法,所以我已经实现了它们。 I have also created a Service layer to handle all business logic and validation, this layer in turn uses the appropriate repository. 我还创建了一个Service层来处理所有业务逻辑和验证,该层依次使用适当的存储库。 Something like this (I am using Simple Injector to inject) 像这样的东西(我使用Simple Injector注入)

DAL LAYER DAL LAYER

public class MyRepository {

    private DbContext _context;
    public MyRepository(DbContext context) {
        _context = context;
    }    

    public MyEntity Get(int id)
    {
        return _context.Set<MyEntity>().Find(id);
    }

    public TEntity Add(MyEntity t)
    {
        _context.Set<MyEntity>().Add(t);
        _context.SaveChanges();
        return t;
    }

    public TEntity Update(MyEntity updated, int key)
    {
        if (updated == null)
            return null;

        MyEntity existing = _context.Set<MyEntity>().Find(key);
        if (existing != null)
        {
            _context.Entry(existing).CurrentValues.SetValues(updated);
            _context.SaveChanges();
        }
        return existing;
    }

    public void Delete(MyEntity t)
    {
        _context.Set<MyEntity>().Remove(t);
        _context.SaveChanges();
    }
}

SERVICE LAYER 服务层

public class MyService {
    private MyRepository _repository;

    public MyService(MyRepository repository) {
        _repository = repository;    
    }

    public MyEntity Get(int id)
    {
        return _repository.Get(id);
    }

    public MyEntity Add(MyEntity t)
    {
        _repository.Add(t);

        return t;
    }

    public MyEntity Update(MyEntity updated)
    {
        return _repository.Update(updated, updated.Id);
    }

    public void Delete(MyEntity t)
    {
        _repository.Delete(t);
    }
}

Now this is very simple, so i can use the following code to update an object. 现在这很简单,所以我可以使用以下代码来更新对象。

MyEntity entity = MyService.Get(123);
MyEntity.Name = "HELLO WORLD";
entity = MyService.Update(entity);

Or this to create an object 或者这个来创建一个对象

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);
// entity.Id is now populated

Now say i needed to update an item based on the creation Id of another, i could use the code above all fine, but what happens if an error occurs? 现在说我需要根据另一个的创建ID更新项目,我可以使用上面的代码都很好,但是如果发生错误会发生什么? I need some sort of transaction/rollback. 我需要某种事务/回滚。 Is this what the Unit Of Work pattern is suppose to solve? 这是工作单元模式应该解决的问题吗?

So i guess i need to have DbContext in my UnitOfWork object, so i create an object like so? 所以我想我需要在我的UnitOfWork对象中使用DbContext,所以我创建一个这样的对象?

public class UnitOfWork : IDisposable {

    private DbContext _context;

    public UnitOfWork(DbContext context) {
        _context = context;
    }

    public Commit() {
        _context.SaveChanges();
    }

    public Dispose() {
        _context.Dispose();
    }

}

Ok so again, thats quite simple. 好的,那很简单。 UnitOfWork holds the context as well ( i use same context on all repositories anyway) and it calls the SaveChanges() method. UnitOfWork也保存上下文(我在所有存储库中使用相同的上下文),它调用SaveChanges()方法。 I would then remove the SaveChanges() method call from my repository. 然后我将从我的存储库中删除SaveChanges()方法调用。 So to add i would do the following: 所以要添加我将执行以下操作:

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);

uow.Commit();

But what if i need to create an object and then update other objects based on that Id, this will now not work, because the Id will not be created until i call Commit on the uow. 但是如果我需要创建一个对象,然后根据该Id更新其他对象,那么现在将无法工作,因为在我调用Uow上的Commit之前不会创建Id。 Example

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);
// entity.Id is NOT populated

MyEntity otherEntity = MyService.Get(123);
otherEntity.OtherProperty = entity.Id;
MyService.Update(otherEntity);

uow.Commit();  // otherEntity.OtherProperty is not linked.....?

So i have a feeling that this UnitOfWork class is not right... maybe i am miss understanding something. 所以我觉得这个UnitOfWork课程不对...也许我想念一些东西。

I need to be able to add an entity and get that Id and use it on another entity, but if an error occurs, i want to "rollback" like an ado.net transaction would do. 我需要能够添加一个实体并获取该Id并在另一个实体上使用它,但如果发生错误,我想像ado.net事务那样“回滚”。

Is this functionality possible using Entity Framework and Repositories? 使用Entity Framework和Repositories可以实现此功能吗?

I have to say first that there is not a unique right way to solve this issue. 我必须首先说, 没有一种独特的正确方法来解决这个问题。 I'm just presenting here what I would probably do. 我只是在这里展示我可能会做的事情。


First thing is, DbContext itself implements the Unit of work pattern . 首先, DbContext本身实现了工作单元模式 Calling SaveChanges does create a DB transaction so every query executed against the DB will be rollbacked is something goes wrong. 调用SaveChanges 创建一个数据库事务,因此对数据库执行的每个查询都将被回滚,这是出错的地方。

Now, there is a major issue in the current design you have: your repository calls SaveChanges on the DbContext . 现在,您当前的设计存在一个主要问题:您的存储库在DbContext上调用SaveChanges This means that you make XXXRepository responsible to commit all the modification you made on the unit of work , not just the modifications on the XXX entities your repository is responsible for. 这意味着您使XXXRepository负责提交您对工作单元所做的所有修改 ,而不仅仅是对您的存储库负责的XXX实体的修改。

Another thing is that DbContext is a repository itself too. 另一件事是DbContext本身也是一个存储库。 So abstracting the DbContext usage inside another repository just creates another abstraction on an existing abstraction, that's just too much code IMO. 因此,在另一个存储库中抽象DbContext用法只会在现有抽象上创建另一个抽象,这就是IMO过多的代码。

Plus the fact you may need to access XXX entities from YYY repository and YYY entities from XXX repository, so to avoid circular dependencies you'll end up with a useless MyRepository : IRepository<TEntity> that just duplicates all the DbSet methods. 此外,您可能需要从YYY存储库和XXX存储库中的YYY实体访问XXX实体,因此为了避免循环依赖,您将最终得到一个无用的MyRepository : IRepository<TEntity>DbSet复制所有DbSet方法。

I would drop the whole repository layer. 我会删除整个存储库层。 I would use the DbContext directly inside the service layer. 我会直接在服务层内使用DbContext Of course, you can factor all complex queries you don't want to duplicate in the service layer. 当然,您可以将所有不希望在服务层中复制的复杂查询考虑在内。 Something like: 就像是:

public MyService()
{
    ...
    public MyEntity Create(some parameters)
    {
        var entity = new MyEntity(some parameters);
        this.context.MyEntities.Add(entity);

        // Actually commits the whole thing in a transaction
        this.context.SaveChanges();

        return entity;
    }

    ...

    // Example of a complex query you want to use multiple times in MyService
    private IQueryable<MyEntity> GetXXXX_business_name_here(parameters)
    {
        return this.context.MyEntities
            .Where(z => ...)
            .....
            ;
    }
}

With this pattern, every public call on a service class is executed inside a transaction thanks to DbContext.SaveChanges being transactional. 使用此模式,由于DbContext.SaveChanges是事务性的, DbContext.SaveChanges在事务内执行服务类上的每个公共调用。

Now for the example you have with the ID that is required after the first entity insertion, one solution is to not use the ID but the entity itself. 现在,对于具有第一个实体插入后所需的ID的示例,一个解决方案是不使用ID而是实体本身。 So you let Entity Framework and its own implementation of the unit of work pattern deal with it. 所以你让Entity Framework和它自己实现的工作单元模式处理它。

So instead of: 所以代替:

var entity = new MyEntity();
entity = mydbcontext.Add(entity);
// what should I put here?
var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherPropertyId = entity.Id;

uow.Commit();

you have: 你有:

var entity = new MyEntity();
entity = mydbcontext.Add(entity);

var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherProperty = entity;     // Assuming you have a navigation property

uow.Commit();

If you don't have a navigation property, or if you have a more complex use case to deal with, the solution is to use the good gold transaction inside your public service method: 如果您没有导航属性,或者您有更复杂的用例需要处理,解决方案是使用公共服务方法中的良好黄金交易:

public MyService()
{
    ...
    public MyEntity Create(some parameters)
    {
        // Encapuslates multiple SaveChanges calls in a single transaction
        // You could use a ITransaction if you don't want to reference System.Transactions directly, but don't think it's really useful
        using (var transaction = new TransactionScope())
        {
            var firstEntity = new MyEntity { some parameters };
            this.context.MyEntities.Add(firstEntity);

            // Pushes to DB, this'll create an ID
            this.context.SaveChanges();

            // Other commands here
            ...

            var newEntity = new MyOtherEntity { xxxxx };
            newEntity.MyProperty = firstEntity.ID;
            this.context.MyOtherEntities.Add(newEntity);

            // Pushes to DB **again**
            this.context.SaveChanges();

            // Commits the whole thing here
            transaction.Commit();

            return firstEntity;
        }
    }
}

You can even call multiple services method inside a transactional scope if required: 如果需要,您甚至可以在事务范围内调用多个服务方法:

public class MyController()
{
    ...

    public ActionResult Foo()
    {
        ...
        using (var transaction = new TransactionScope())
        {
            this.myUserService.CreateUser(...);
            this.myCustomerService.CreateOrder(...);

            transaction.Commit();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM