通用存储库、工作单元、Unity 的架构问题

Posted

技术标签:

【中文标题】通用存储库、工作单元、Unity 的架构问题【英文标题】:Architectural issue with Generic Repository, Unit Of Work, Unity 【发布时间】:2019-01-06 14:49:36 【问题描述】:

我正在使用 MVC5、EntityFramework、Unity、UnitOfWork 和 Generic Repository 开发我的项目架构之一,而且我还没有使用 AutoMapper 或任何其他类似于 AutoMapper 的东西。

每当我第一次执行更新时,它都能完美运行,但第二次之后,它会出现以下错误。

附加类型为“EntityModel.tblCompanyMaster”的实体失败,因为同一类型的另一个实体已经具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新实体,尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图形,然后根据需要将非新实体的状态设置为“未更改”或“已修改”。

此处更新方法发生错误 [_dbSet.Attach(entity)]

 public virtual void UpdateEntity(TEntity entity, string[] NoUpdateProperty = null)
    
        _dbSet.Attach(entity);
        _dbContext.Entry<TEntity>(entity).State = EntityState.Modified;
      .
      .
     

这是我的代码:

1) 团结

public static class UnityConfig

    public static void RegisterComponents()
    
        var container = new UnityContainer();
        //container.RegisterType<DbContext, dbTestCMSEntities>();
        container.RegisterSingleton<IUnitOfWork, UnitOfWork>();
        container.RegisterSingleton(typeof(IDbHelper<>), typeof(DbHelper<>));            
        container.RegisterSingleton<ICompanyMasterBL, tblCompanyMasterBL>();            
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    

2) 工作单元

    public interface IUnitOfWork

    dbTestCMSEntities dbContext  get; 
    void Save();


public class UnitOfWork : IUnitOfWork, IDisposable

    public dbTestCMSEntities dbContext  get; 
    private bool _disposed = false;

    public UnitOfWork(dbTestCMSEntities context)
    
        dbContext = context;
    

    public void Save()
    
        try
        
            dbContext.SaveChanges();
        
        catch (Exception ex)
        
            throw ex;
        
    

    protected virtual void Dispose(bool disposing)
    
        if (!this._disposed)
        
            if (disposing)
            
                dbContext.Dispose();
            
        
        this._disposed = true;
    

    public void Dispose()
    
        Dispose(true);
        GC.SuppressFinalize(this);
    

3) 通用存储库

public interface IDbHelper<TEntity> where TEntity : class

    bool Exists(object pkId);
    TEntity GetFirst(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, dynamic>> order = null);
    IEnumerable<TEntity> GetMany(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, string>> order = null);
    IEnumerable<TEntity> GetPagedList(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, Int64>> _order, int currentPageIndex, out int Total);
    void InsertEntity(TEntity entity);
    void InsertEntity(List<TEntity> entity);
    void UpdateEntity(TEntity entity, string[] NoUpdateProperty = null);
    void UpdateEntity(List<TEntity> entity, string[] NoUpdateProperty = null);
    void Delete(object id);
    void Delete(TEntity entity);
    void DeleteEntity(Expression<Func<TEntity, bool>> condition);


public class DbHelper<TEntity> : IDbHelper<TEntity> where TEntity : class

    protected readonly dbTestCMSEntities _dbContext;
    protected DbSet<TEntity> _dbSet;
    public DbHelper(IUnitOfWork unitOfWork)
    
        _dbContext = unitOfWork.dbContext;
        _dbSet = _dbContext.Set<TEntity>();
    

    public bool Exists(object pkId)
    
        return _dbSet.Find(pkId) != null;
    

    public TEntity GetFirst(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, dynamic>> order = null)
    
        IQueryable<TEntity> lstResult = _dbSet.AsNoTracking().AsQueryable();
        if (order != null)
            lstResult = lstResult.AsNoTracking().OrderBy(order).AsQueryable();
        if (condition != null)
            lstResult = lstResult.AsNoTracking().Where(condition).AsQueryable();

        TEntity entity = lstResult.AsNoTracking().FirstOrDefault();
        return entity;
    

    public virtual IEnumerable<TEntity> GetMany(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, string>> order = null)
    
        IQueryable<TEntity> lstResult = _dbSet.AsNoTracking().AsQueryable();
        if (condition != null)
            lstResult = lstResult.AsNoTracking().Where(condition).AsQueryable();
        if (order != null)
            lstResult = lstResult.AsNoTracking().OrderBy(order).AsQueryable();
        return lstResult;
    

    public virtual IEnumerable<TEntity> GetPagedList(Expression<Func<TEntity, bool>> condition, Expression<Func<TEntity, Int64>> _order, int currentPageIndex, out int Total)
    
        IQueryable<TEntity> lstResult = _dbSet.AsNoTracking().AsQueryable();
        if (condition != null)
            lstResult = lstResult.AsNoTracking().Where(condition).AsQueryable();
        Total = lstResult.Count();
        lstResult = lstResult.AsNoTracking().OrderByDescending(_order).AsQueryable().Skip((currentPageIndex - 1) * StringUtility.ItemsPerPage).Take(StringUtility.ItemsPerPage);
        return lstResult;
    

    /// <summary>
    /// Insert single record
    /// </summary>
    /// <param name="entity"></param>
    public virtual void InsertEntity(TEntity entity)
    
        _dbSet.Add(entity);
    

    /// <summary>
    /// Insert multiple records
    /// </summary>
    /// <param name="entity"></param>
    public virtual void InsertEntity(List<TEntity> entity)
    
        _dbSet.AddRange(entity);
    

    /// <summary>
    /// Update single entity
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="NoUpdateProperty"></param>
    public virtual void UpdateEntity(TEntity entity, string[] NoUpdateProperty = null)
    
        _dbSet.Attach(entity);
        _dbContext.Entry<TEntity>(entity).State = EntityState.Modified;
        if (NoUpdateProperty != null)
        
            foreach (string item in NoUpdateProperty)
            
                _dbContext.Entry<TEntity>(entity).Property(item).IsModified = false;
            
        
    

    /// <summary>
    /// Update multiple entity
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="NoUpdateProperty"></param>
    public virtual void UpdateEntity(List<TEntity> entity, string[] NoUpdateProperty = null)
    
        foreach (TEntity item in entity)
        
            _dbSet.Attach(item);
            _dbContext.Entry<TEntity>(item).State = EntityState.Modified;
            if (NoUpdateProperty != null)
            
                foreach (string item1 in NoUpdateProperty)
                
                    _dbContext.Entry<TEntity>(item).Property(item1).IsModified = false;
                
            
        
    

    /// <summary>
    /// Generic Delete method for the entities. Delete one record only.
    /// </summary>
    /// <param name="id"></param>
    public virtual void Delete(object id)
    
        TEntity entityToDelete = _dbSet.Find(id);
        Delete(entityToDelete);
    

    /// <summary>
    /// Generic Delete method for the entities. Delete one record only.
    /// </summary>
    /// <param name="entityToDelete"></param>
    public virtual void Delete(TEntity entity)
    
        if (_dbContext.Entry(entity).State == EntityState.Detached)
        
            _dbSet.Attach(entity);
        
        _dbSet.Remove(entity);
    

    /// <summary>
    /// Delete one or many records based on given condition
    /// </summary>
    /// <param name="condition"></param>
    public void DeleteEntity(Expression<Func<TEntity, bool>> condition)
    
        _dbSet.RemoveRange(_dbSet.Where(condition));
    

我尝试了很多方法,但没有帮助我,我相信我在 Unity 和 Unit Of Work 上做错了,但很难识别。提前感谢您帮助我。

【问题讨论】:

throw ex; 是一种不好的做法。如果您没有对它做任何事情,请不要捕获异常。 【参考方案1】:

每当我第一次执行更新时,它都能完美运行,但第二次之后,它会给我以下错误。

您可能正在尝试为多个请求重用 DbContext 实例。您的 DI 容器似乎将事物注册为单例。 DbContext 应按请求确定范围。

【讨论】:

我已经尝试将 DbContext 范围添加到每个请求但没有运气,可能我做错了,你能建议你在 UnityConfig 中进行确切的更改吗?【参考方案2】:

下面的代码帮助我找到我的解决方案,只需在 Attach() while update 之前调用此方法:

public Boolean Exists(T entity) 
var objContext = ((IObjectContextAdapter)this._dbContext).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);

Object foundEntity;
var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity);

if (exists) 
    objContext.Detach(foundEntity);

return (exists);

【讨论】:

这不是一个好习惯。使用类似 ORM 的实体框架,您可以更好地了解 UoW 的工作原理。分离实体并重新连接它是没有意义的。为什么不修改附加的实体本身?

以上是关于通用存储库、工作单元、Unity 的架构问题的主要内容,如果未能解决你的问题,请参考以下文章

实体框架使用 Codefirst、通用存储库、工作单元模式保存多对多关系

存储库和工作单元模式 - 如何保存更改

带有存储库和工作单元的 ASP.NET 标识

如何在 ASP MVC 中实现工作单元、存储库和业务逻辑?

text 存储库/工作单元 - 依赖注入

如何使用 asp.net 核心中的依赖注入在工作单元模式中延迟注入存储库