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

本文关键字:net ado 交易 它像 框架 工作 何时何地 单元 实体 | 更新日期: 2023-09-27 18:24:41

创建一个新的MVC项目,喜欢数据层中存储库的想法,所以我已经实现了它们。我还创建了一个服务层来处理所有业务逻辑和验证,该层反过来使用适当的存储库。类似这样的东西(我使用Simple Injector来注入)

DAL层

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();
    }
}

服务层

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);
    }
}

现在这很简单,所以我可以使用下面的代码来更新对象。

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

或者这样创建一个对象

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

现在假设我需要根据另一个项目的创建Id更新一个项目,我可以使用代码,但如果发生错误会发生什么?我需要某种事务/回滚。这就是工作单元模式应该解决的问题吗?

所以我想我需要在UnitOfWork对象中有DbContext,所以我创建了一个这样的对象?

public class UnitOfWork : IDisposable {
    private DbContext _context;
    public UnitOfWork(DbContext context) {
        _context = context;
    }
    public Commit() {
        _context.SaveChanges();
    }
    public Dispose() {
        _context.Dispose();
    }
}

好的,再说一遍,这很简单。UnitOfWork也保存上下文(我在所有存储库中都使用相同的上下文),它调用SaveChanges()方法。然后,我会从我的存储库中删除SaveChanges()方法调用。因此,我将添加以下内容:

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();

但是,如果我需要创建一个对象,然后根据该Id更新其他对象,这将不起作用,因为在我调用uow上的Commit之前,不会创建该Id。示例

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.....?

所以我有一种感觉,这个UnitOfWork类是不对的。。。也许我错过了理解一些东西。

我需要能够添加一个实体,获得该Id并在另一个实体上使用,但如果发生错误,我想像ado.net事务那样"回滚"。

使用实体框架和存储库是否可以实现此功能?

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

我必须首先说,没有唯一正确的方法来解决这个问题。我只是在这里介绍我可能会做的事情。


首先,DbContext本身实现了工作单元模式。调用SaveChanges确实创建了一个数据库事务,因此对数据库执行的每个查询都将被回滚。

现在,在当前的设计中有一个主要问题:您的存储库在DbContext上调用SaveChanges。这意味着您让XXXRepository负责提交您对工作单元所做的所有修改,而不仅仅是对您的存储库负责的XXX实体所做的修改。

另一件事是DbContext本身也是一个存储库。因此,在另一个存储库中抽象DbContext的使用只会在现有抽象的基础上创建另一个抽象,这太多了

此外,您可能需要从YYY存储库访问XXX实体,从XXX存储库访问YYY实体,因此为了避免循环依赖,您最终会得到一个无用的MyRepository : IRepository<TEntity>,它只复制了所有DbSet方法。

我会去掉整个存储库层。我会直接在服务层内部使用DbContext。当然,您可以考虑所有不想在服务层中重复的复杂查询。类似于:

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 => ...)
            .....
            ;
    }
}

使用这种模式,由于DbContext.SaveChanges是事务性的,所以对服务类的每个公共调用都在事务内执行。

现在,对于第一次插入实体后所需的ID示例,一种解决方案是不使用ID,而是使用实体本身。所以你让实体框架和它自己的工作单元模式实现来处理它

所以不是:

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();

你有:

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();

如果你没有导航属性,或者你有更复杂的用例需要处理,解决方案是在你的公共服务方法中使用好的黄金交易:

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;
        }
    }
}

如果需要,您甚至可以在事务范围内调用多个服务方法:

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