Gil*_*rdo 32 c# asp.net-mvc entity-framework unit-of-work
创建一个新的MVC项目,并喜欢数据层中的存储库的想法,所以我已经实现了它们.我还创建了一个Service层来处理所有业务逻辑和验证,该层依次使用适当的存储库.像这样的东西(我使用Simple Injector注入)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
服务层
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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在这很简单,所以我可以使用以下代码来更新对象.
MyEntity entity = MyService.Get(123);
MyEntity.Name = "HELLO WORLD";
entity = MyService.Update(entity);
Run Code Online (Sandbox Code Playgroud)
或者这个来创建一个对象
MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);
// entity.Id is now populated
Run Code Online (Sandbox Code Playgroud)
现在说我需要根据另一个的创建ID更新项目,我可以使用上面的代码都很好,但是如果发生错误会发生什么?我需要某种事务/回滚.这是工作单元模式应该解决的问题吗?
所以我想我需要在我的UnitOfWork对象中使用DbContext,所以我创建一个这样的对象?
public class UnitOfWork : IDisposable {
private DbContext _context;
public UnitOfWork(DbContext context) {
_context = context;
}
public Commit() {
_context.SaveChanges();
}
public Dispose() {
_context.Dispose();
}
}
Run Code Online (Sandbox Code Playgroud)
好的,那很简单.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();
Run Code Online (Sandbox Code Playgroud)
但是如果我需要创建一个对象,然后根据该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.....?
Run Code Online (Sandbox Code Playgroud)
所以我觉得这个UnitOfWork课程不对......也许我想念一些东西.
我需要能够添加一个实体并获取该Id并在另一个实体上使用它,但如果发生错误,我想像ado.net事务那样"回滚".
使用Entity Framework和Repositories可以实现此功能吗?
ken*_*n2k 43
我必须首先说,没有一种独特的正确方法来解决这个问题.我只是在这里展示我可能会做的事情.
首先,DbContext它本身实现了工作单元模式.调用SaveChanges 确实会创建一个数据库事务,因此对数据库执行的每个查询都将被回滚,这是出错的地方.
现在,您当前的设计存在一个主要问题:您的存储库调用SaveChanges了DbContext.这意味着您有XXXRepository责任提交您对工作单元所做的所有修改,而不仅仅是对您的存储库负责的XXX实体的修改.
另一件事是它DbContext本身也是一个存储库.因此,DbContext在另一个存储库中抽象使用只会在现有抽象上创建另一个抽象,这就是IMO过多的代码.
此外,您可能需要从YYY存储库和XXX存储库中的YYY实体访问XXX实体,因此为了避免循环依赖,您将最终得到一个无用的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 => ...)
.....
;
}
}
Run Code Online (Sandbox Code Playgroud)
使用此模式,服务类上的每个公共调用都在事务内执行,这要归功于DbContext.SaveChanges事务性.
现在,对于具有第一个实体插入后所需的ID的示例,一个解决方案是不使用ID而是实体本身.所以你让Entity Framework和它自己实现的工作单元模式处理它.
所以代替:
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();
Run Code Online (Sandbox Code Playgroud)
你有:
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();
Run Code Online (Sandbox Code Playgroud)
如果您没有导航属性,或者您有更复杂的用例需要处理,解决方案是使用公共服务方法中的良好黄金交易:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果需要,您甚至可以在事务范围内调用多个服务方法:
public class MyController()
{
...
public ActionResult Foo()
{
...
using (var transaction = new TransactionScope())
{
this.myUserService.CreateUser(...);
this.myCustomerService.CreateOrder(...);
transaction.Commit();
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
28831 次 |
| 最近记录: |