C# 存储库模式&x2B;依赖性注入&x2B;工作单位+;环境足迹
关于S/O,有很多类似的问题,但这一个问题有一个具体的问题,我没有看到解决: 这是一个MVC应用程序。我正在使用依赖注入(简单的注入,尽管我认为它不相关),它注入每个Web请求 我面临的主要问题是,因为我的UoW是按web请求注入的,所以我无法在添加数据时失败并继续,这是我最近需要的 以下代码说明: 数据层C# 存储库模式&x2B;依赖性注入&x2B;工作单位+;环境足迹,c#,entity-framework,dependency-injection,repository-pattern,unit-of-work,C#,Entity Framework,Dependency Injection,Repository Pattern,Unit Of Work,关于S/O,有很多类似的问题,但这一个问题有一个具体的问题,我没有看到解决: 这是一个MVC应用程序。我正在使用依赖注入(简单的注入,尽管我认为它不相关),它注入每个Web请求 我面临的主要问题是,因为我的UoW是按web请求注入的,所以我无法在添加数据时失败并继续,这是我最近需要的 以下代码说明: 数据层 public abstract RepositoryBase<TEntity> { private readonly MyDbContext _context;
public abstract RepositoryBase<TEntity>
{
private readonly MyDbContext _context;
//fields set from contrstuctor injection
protected RepositoryBase(MyDbContext context)
{
_context = context;
}
public IList<TEntity> GetAll()
{
return _context.Set<TEntity>().ToList();
}
public TEntity GetById(Int32 id)
{
_context.Set<TEntity>().Find(id);
}
public TEntity Insert(TEntity entity)
{
_context.Set<TEntity>().Add(entity);
}
}
public UserRepository : RepositoryBase<User>, IUserRepository
{
//constructor injection
public UserRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String name, String email, Int32 ageYears)
{
var entity = GetById(id);
entity.Name = name;
entity.Email = email;
entity.Age = ageYears;
}
public UpdateName(Int32 id, String name)
{
var entity = GetById(id);
entity.Name = name;
}
}
public AddressRepository : RepositoryBase<Address>, IAddressRepository
{
//constructor injection
public AddressRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String street, String city)
{
var entity = GetById(id);
entity.Street = street;
entity.City = city;
}
public Address GetForUser(Int32 userId)
{
return _context.Adresses.FirstOrDefault(x => x.UserId = userId);
}
}
public DocumentRepository : RepositoryBase<Document>, IDocumentRepository
{
//constructor injection
public DocumentRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String newTitle, String newContent)
{
var entity.GetById(id);
entity.Title = newTitle;
entity.Content = newContent;
}
public IList<Document> GetForUser(Int32 userId)
{
return _context.Documents.Where(x => x.UserId == userId).ToList();
}
}
public UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
//fields set from contrstuctor injection
public UnitOfWork(MyDbContext context)
{
_context = context;
}
public Int32 Save()
{
return _context.SaveChanges();
}
public ITransaction StartTransaction()
{
return new Transaction(_context.Database.BeginTransaction(IsolationLevel.ReadUncommitted));
}
}
public Transaction : ITransaction
{
private readonly DbContextTransaction _transaction;
public Transaction(DbContextTransaction t)
{
_transaction = t;
State = TransactionState.Open;
}
public void Dispose()
{
if (_transaction != null)
{
if (State == TransactionState.Open)
{
Rollback();
}
_transaction.Dispose();
}
}
public TransactionState State { get; private set; }
public void Commit()
{
try
{
_transaction.Commit();
State = TransactionState.Committed;
}
catch (Exception)
{
State = TransactionState.FailedCommitRolledback;
throw;
}
}
public void Rollback()
{
if (_transaction.UnderlyingTransaction.Connection != null)
{
_transaction.Rollback();
}
State = TransactionState.Rolledback;
}
}
public DocumentService : IDocumentService
{
//fields set from contrstuctor injection
private readonly IDocumentRepository _docRepo;
private readonly IUnitOfWork _unitOfWork;
public void AuthorNameChangeAddendum(Int32 userId, String newAuthorName)
{
//this works ok if error thrown
foreach(var doc in _docRepo.GetForUser(userId))
{
var addendum = $"\nAddendum: As of {DateTime.Now} the author will be known as {newAuthorName}.";
_docRepo.Update(documentId, doc.Title + "-Processed", doc.Content + addendum);
}
_unitOfWork.Save();
}
}
public UserService
{
//fields set from contrstuctor injection
private readonly IUserRepository _userRepo;
private readonly IAddressRepository _addressRepo;
private readonly IUnitOfWork _unitOfWork;
private readonly IDocumentService _documentService;
public void ChangeUser(Int32 userId, String newName, String newStreet, String newCity)
{
//this works ok if error thrown
_userRepo.UpdateName(userId, newName);
var address = _addressRepo.GetForUser(userId);
_addressRepo.Update(address.AddressId, newStreet, newCity);
_unitOfWork.Save();
}
public void ChangeUserAndProcessDocs(Int32 userId, String newName, Int32)
{
//this is ok because of transaction
using(var transaction = _unitOfWork.StartTransaction())
{
_documentService.AuthorNameChangeAddendum(userId, newName); //this function calls save() on uow
//possible exception here could leave docs with an inaccurate addendum, so transaction needed
var x = 1/0;
_userRepo.UpdateName(userId, newName);
_unitOfWork.Save();
transaction.Commit();
}
}
//THE PROBLEM:
public IList<String> AddLastNameToAll(String lastName)
{
var results = new List<String>();
foreach(var u in _userRepo.GetAll())
{
try
{
var newName = $"{lastName}, {u.Name}";
_userRepo.UpdateName(u.UserId, newName);
_unitOfWork.Save(); //throws validation exception
results.Add($"Changed name from {u.Name} to {newName}.");
}
catch(DbValidationException e)
{
results.Add($"Error adding last name to {u.Name}: {e.Message}");
//but all subsequeqnet name changes will fail because the invalid entity will be stuck in the context
}
}
return results;
}
}
这感觉很奇怪,因为现在这个函数的行为与我的所有其他存储库操作完全不同,并且不会遵守事务
是否有UoW+EF+存储库模式+DI的实现可以解决这个问题?它是如何工作的:
public class DbFactory : Disposable, IDbFactory
{
HomeCinemaContext dbContext;
public HomeCinemaContext Init()
{
return dbContext ?? (dbContext = new HomeCinemaContext());
}
protected override void DisposeCore()
{
if (dbContext != null)
dbContext.Dispose();
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory dbFactory;
private HomeCinemaContext dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this.dbFactory = dbFactory;
}
public HomeCinemaContext DbContext
{
get { return dbContext ?? (dbContext = dbFactory.Init()); }
}
public void Commit()
{
DbContext.Commit();
}
}
public class EntityBaseRepository<T> : IEntityBaseRepository<T>
where T : class, IEntityBase, new()
{
private HomeCinemaContext dataContext;
#region Properties
protected IDbFactory DbFactory
{
get;
private set;
}
protected HomeCinemaContext DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
public EntityBaseRepository(IDbFactory dbFactory)
{
DbFactory = dbFactory;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return DbContext.Set<T>();
}
public virtual IQueryable<T> All
{
get
{
return GetAll();
}
}
public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = DbContext.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public T GetSingle(int id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return DbContext.Set<T>().Where(predicate);
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
DbContext.Set<T>().Add(entity);
}
public virtual void Edit(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
dbEntityEntry.State = EntityState.Deleted;
}
}
公共类DbFactory:一次性的,IDbFactory
{
家庭背景;
公共HomeCinemaContext Init()
{
返回dbContext??(dbContext=newHomeCinemaContext());
}
受保护的覆盖void DisposeCore()
{
if(dbContext!=null)
dbContext.Dispose();
}
}
公共类UnitOfWork:IUnitOfWork
{
私有只读IDbFactory dbFactory;
私人家庭背景;
公共工作单元(IDbFactory dbFactory)
{
this.dbFactory=dbFactory;
}
公共HomeCinemaContext数据库上下文
{
获取{return dbContext???(dbContext=dbFactory.Init());}
}
公共无效提交()
{
提交();
}
}
公共类EntityBaseRepository:IEntityBaseRepository
其中T:class,IEntityBase,new()
{
私有HomeContext数据上下文;
#区域属性
受保护的IDbFactory数据库工厂
{
得到;
私人设置;
}
受保护的HomeCinemaContext数据库上下文
{
获取{return dataContext???(dataContext=DbFactory.Init());}
}
公共EntityBaseRepository(IDbFactory dbFactory)
{
DbFactory=DbFactory;
}
#端区
公共虚拟IQueryable GetAll()
{
返回DbContext.Set();
}
公共虚拟IQueryable All
{
得到
{
返回GetAll();
}
}
公共虚拟可查询AllIncluding(参数表达式[]包含属性)
{
IQueryable query=DbContext.Set();
foreach(includeProperty中的var includeProperty)
{
query=query.Include(includeProperty);
}
返回查询;
}
公共T GetSingle(内部id)
{
返回GetAll().FirstOrDefault(x=>x.ID==ID);
}
公共虚拟IQueryable FindBy(表达式谓词)
{
返回DbContext.Set().Where(谓词);
}
公共虚拟空添加(T实体)
{
DbEntityEntry DbEntityEntry=DbContext.Entry(实体);
DbContext.Set().Add(实体);
}
公共虚拟无效编辑(T实体)
{
DbEntityEntry DbEntityEntry=DbContext.Entry(实体);
dbEntityEntry.State=EntityState.Modified;
}
公共虚拟作废删除(T实体)
{
DbEntityEntry DbEntityEntry=DbContext.Entry(实体);
dbEntityEntry.State=EntityState.Deleted;
}
}
主类是DbFactory,它只包含EF db context的一个实例。所以,无论您在不同的存储库中做什么,应用程序总是使用一个上下文
类EntityBaseRepository也可以在DbFactory提供的相同db上下文上运行
UnitOfWork被传递给控制器只是为了能够使用方法Commit保存对数据库的所有更改,并在db上下文的同一实例上工作
您可能不需要在代码中使用事务
这里有完整的教程:
public class DbFactory : Disposable, IDbFactory
{
HomeCinemaContext dbContext;
public HomeCinemaContext Init()
{
return dbContext ?? (dbContext = new HomeCinemaContext());
}
protected override void DisposeCore()
{
if (dbContext != null)
dbContext.Dispose();
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory dbFactory;
private HomeCinemaContext dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this.dbFactory = dbFactory;
}
public HomeCinemaContext DbContext
{
get { return dbContext ?? (dbContext = dbFactory.Init()); }
}
public void Commit()
{
DbContext.Commit();
}
}
public class EntityBaseRepository<T> : IEntityBaseRepository<T>
where T : class, IEntityBase, new()
{
private HomeCinemaContext dataContext;
#region Properties
protected IDbFactory DbFactory
{
get;
private set;
}
protected HomeCinemaContext DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
public EntityBaseRepository(IDbFactory dbFactory)
{
DbFactory = dbFactory;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return DbContext.Set<T>();
}
public virtual IQueryable<T> All
{
get
{
return GetAll();
}
}
public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = DbContext.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public T GetSingle(int id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return DbContext.Set<T>().Where(predicate);
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
DbContext.Set<T>().Add(entity);
}
public virtual void Edit(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
dbEntityEntry.State = EntityState.Deleted;
}
}
\
查找单词:“DbFactory”或“UnitOfWork”以了解详细信息。让它立即提交每个更改的解决方案是“立即存储库包装器”。这允许我重用现有代码,并允许我在测试服务时继续轻松模拟存储库行为。用户的即时存储库包装示例如下所示:
public interface IUserImmediateRepository
{
void UpdateName(Int32 id, String newName);
}
public class UserImmediateRepository : IUserImmediateRepository
{
public UserImmediateRepository()
{
}
public void UpdateName(Int32 id, String newName)
{
using(var singleUseContext = new MyDbContext())
{
var repo = new UserRepository(singleUseContext);
repo.UpdateName(id, newName);
singleUseContext.SaveChanges();
}
}
}
这在罕见的批量处理场景中非常有效,我需要立即提交。非常确定您希望每个工作单元有一个DB上下文,而不是整个应用程序有一个上下文。如果连接中断会发生什么情况?不是每个应用程序,而是每个用户/请求。它应该由IOC容器完成。这不是您所说的“…DbFactory,它只包含一个EF db上下文实例…应用程序始终使用一个上下文”,并且注入的
IDbFactory
总是通过DbFactory.Init
返回相同的实例。你们需要展示工厂的生命周期是如何被管理的,那个么我可能不会称之为工厂。签出ok:),你是对的。所以它需要一些改变。它需要每个用户/请求一个实例。这将由IOC和每个请求的对象定义来完成。这就是我的朋友:)你知道EF默认实现UoW和Repo模式吗?DbContext是UoW,DbSet是Repos。我的观点;向EF添加更多抽象是浪费时间。我知道DbContext是一个UoW。为了避免EF泄漏到服务层,我只在其上添加了一个薄包装。它周围的repo包装器帮助我通过一些存储库操作和警察坏连接等来收集我的所有信息。如果有更好的方法来完成这些事情,我愿意听取。AddLastNameToAll()中提出的问题实际上发生在一个相当专业的领域