简体   繁体   English

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

[英]Architectural issue with Generic Repository, Unit Of Work, Unity

I am working on one of my project architecture using MVC5, EntityFramework, Unity, UnitOfWork and Generic Repository also i have not used AutoMapper or anything else similar to AutoMapper.我正在使用 MVC5、EntityFramework、Unity、UnitOfWork 和 Generic Repository 处理我的项目架构之一,我还没有使用 AutoMapper 或任何其他类似于 AutoMapper 的东西。

Whenever I perform update for first time it works perfectly, but for second time onwards it gives me below error.每当我第一次执行更新时,它都能完美运行,但第二次之后,它会出现以下错误。

Attaching an entity of type 'EntityModel.tblCompanyMaster' failed because another entity of the same type already has the same primary key value.附加类型为“EntityModel.tblCompanyMaster”的实体失败,因为同一类型的另一个实体已具有相同的主键值。 This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values.如果图中的任何实体具有冲突的键值,则在使用“Attach”方法或将实体状态设置为“Unchanged”或“Modified”时可能会发生这种情况。 This may be because some entities are new and have not yet received database-generated key values.这可能是因为某些实体是新的,尚未收到数据库生成的键值。 In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.在这种情况下,使用“添加”方法或“添加”实体状态来跟踪图形,然后根据需要将非新实体的状态设置为“未更改”或“已修改”。

Error occurs in update method right here [_dbSet.Attach(entity)]此处的更新方法中发生错误 [_dbSet.Attach(entity)]

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

Here are my Code:这是我的代码:

1) Unity 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) Unit Of Work 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) Generic Repository 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));
    }
}

I have tried many ways but doesn't help me out, i believe i did something wrong with Unity & Unit Of Work, but hard to identify.我尝试了很多方法,但没有帮助我,我相信我在 Unity 和 Unit Of Work 上做错了,但很难识别。 Thanks in advance to help me out.提前感谢帮助我。

Error is very clear. 错误非常明显。 Following is the quote from here : 以下是这里的报价:

Attaches the given entity to the context underlying the set. 将给定实体附加到集合的基础上下文中。 That is, the entity is placed into the context in the Unchanged state, just as if it had been read from the database. 也就是说,将实体以未更改状态放置到上下文中,就像已从数据库中读取实体一样。
..... .....
..... .....
Attach is a no-op if the entity is already in the context in the Unchanged state. 如果实体已经处于Unchanged状态的上下文中,则Attach为空。

When you call _dbSet.Attach(entity); 当您调用_dbSet.Attach(entity); first time, your detached entity become part of DbSet ; 第一次,您的分离entity成为DbSet一部分; ie it is placed into the context. 即它被放置在上下文中。 This entity also have unique identifier (primary key value) which is used to identify an entity. 该实体还具有用于标识实体的唯一标识符(主键值)。

Now, when you call it for second time, identifier/key is same. 现在,当您第二次调用它时,标识符/密钥是相同的。 Two entities with same identifier cannot exist in one DbSet . 一个DbSet不能存在具有相同标识符的两个实体。

You should check before calling Attach if the entity already exists. 您应在调用Attach之前检查该实体是否已存在。

Use _dbSet.Find(key) before calling Attach . 在调用Attach之前使用_dbSet.Find(key) If already exist, do not call Attach . 如果已经存在,请不要调用Attach This may be helpful. 可能会有所帮助。


Not the part of your question, but I guess the way you are updating the record is not correct. 这不是问题的一部分,但是我想您更新记录的方式不正确。 If you want to update the record, better way is to get ( Find / SingleOrDefault ) it from the database => modify the property(ies) you want to change => flush (call SaveChanges ) the changes. 如果要更新记录,更好的方法是从数据库中获取( Find / SingleOrDefault )=>修改要更改的属性=>刷新(调用SaveChanges )更改。 Please refer to this question. 请参考这个问题。

Whenever I perform update for first time it works perfectly, but for second time onwards it gives me below error. 每当我第一次执行更新时,它都可以正常工作,但是第二次之后,它会给我以下错误。

Probably you are attempting to re-use a DbContext instance for multiple requests. 可能您正在尝试将DbContext实例重用于多个请求。 Your DI container appears to be registering things as singletons. 您的DI容器似乎正在将事物注册为单例。 The DbContext should be scoped per-request. DbContext应该在每个请求范围内。

Below code helps me to find my solution, just call this method before Attach() while update: 下面的代码可以帮助我找到解决方案,只需在更新时在Attach()之前调用此方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM