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

牧云@^-^@ 提交于 2019-11-27 11:07:50

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. Calling SaveChanges does create a DB transaction so every query executed against the DB will be rollbacked is something goes wrong.

Now, there is a major issue in the current design you have: your repository calls SaveChanges on the DbContext. 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.

Another thing is that DbContext is a repository itself too. So abstracting the DbContext usage inside another repository just creates another abstraction on an existing abstraction, that's just too much code 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.

I would drop the whole repository layer. I would use the DbContext directly inside the service layer. 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.

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. So you let Entity Framework and its own implementation of the unit of work pattern deal with it.

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();
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!