通用存储库添加重复的子实体

Generic repository add duplicates child entities

我在将通用存储库与 Entity Framework 一起使用时遇到问题,特别是在添加实体时。这是回购界面:

 public interface IRepository<TEntity, in TKey> where TEntity : class
 {
   IQueryable<TEntity> GetQueryable();
   IEnumerable<TEntity> GetAll();
   IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
   TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
   TEntity First(Expression<Func<TEntity, bool>> predicate);
   TEntity Single(Expression<Func<TEntity, bool>> predicate);
   TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate);
   void Add(TEntity entity);
   void Attach(TEntity entity);
   void Delete(TEntity entity);
}

我的 EfRepository 实现:

public class EFRepository<TEntity,TKey> : IRepository<TEntity,TKey> where TEntity : class
{
    private readonly DbSet<TEntity> _dbSet;
    private readonly DbContext _context;

    public EFRepository(DbSet<TEntity> dbSet,DbContext context)
    {
        _dbSet = dbSet;
        _context = context;
    }

    public IQueryable<TEntity> GetQueryable()
    {
        return _dbSet.AsQueryable();
    }

    public IEnumerable<TEntity> GetAll()
    {
        return _dbSet;
    }

    public IQueryable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where(predicate);
    }

    public TEntity FirstOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.FirstOrDefault(predicate);
    }

    public TEntity First(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.First(predicate);
    }

    public TEntity Single(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Single(predicate);
    }

    public TEntity SingleOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.SingleOrDefault(predicate);
    }

    public void Add(TEntity entity)
    {
        _dbSet.Add(entity);
    }

    public void Attach(TEntity entity)
    {

        _dbSet.Attach(entity);
        _context.Entry(entity).State = EntityState.Modified;
    }

    public void Delete(TEntity entity)
    {
        _context.Set<TEntity>().Attach(entity);
        _context.Entry(entity).State = EntityState.Deleted;
    }

如果我的 DbSet 与其他 table 没有任何关系,这一切都很好。但是说以下内容:

class TopType
{
    public TopType()
    {
      InnerTypes = new HashSet<InnerType>();
    }

    public int id { get; set;}
    public string something { get; set;}
    public virtual ICollection<InnerType> InnerTypes { get; set;}
}

class InnerType
{
    public InnerType()
    {
       ChildTypes = new HashSet<ChildType>();
    }
    public int id { get; set;}
    public nullable<int> TopTypeId { get; set;}
    public string somethingElse { get; set;}

    public virtual TopType { get; set;}
    public virtual ICollection<ChildType> ChildTypes { get; set;}
}

class ChildType
{
    public ChildType()
    {
       InnerTypes = new HashSet<InnerType>();
     }
    public int id { get; set;}
    public string somethingForTheChild { get; set;}
    public virtual ICollection<InnerType> InnerTypes { get; set;}
 }

我在数据库中已经有一些子类型。这些将返回到 Web 客户端。 Web 客户端的用户创建一个新的 TopType,然后添加任意数量的 InnerType。对于每个内部类型,他们可以 select 已经存在于数据库中并填充了 id 等的子类型。客户端代码正确设置对象并填充所有类型的导航属性。

在服务层我有一个工作单元如下:

public class WorkoutUnitOfWork : IWorkoutUnitOfWork
{
    private WorkoutEntities entities;

    public WorkoutUnitOfWork()
    {
        entities = new WorkoutEntities();
        entities.Configuration.ProxyCreationEnabled = false;  
    }

    private IRepository<TopType, int> topTypeRepository;
    private IRepository<InnerType, int> innerTypeRepository;
    private IRepository<ChildType, int> childTypeRepository;

    public IRepository<Workout, int> TopTypeRepository
    {
        get { return topTypeRepository ?? new EFRepository<TopType, int>(entities.TopTypes, entities); }
    }

    public IRepository<InnerType, int> InnerTypeRepository
    {
        get { return innerTypeRepository ?? new EFRepository<InnerType, int>(entities.InnerTypes, entities); }
    }

    public IRepository<ChildType, int> ChildTypeRepository
    {
        get { return childTypeRepository ?? new EFRepository<ChildType,int>(entities.ChildTypes, entities); }
    }
    public void Commit()
    {
        entities.SaveChanges();
    }

当我通过在存储库上调用 Add 的填充 TopType 时,TopType 与 InnerTypes 一样被添加到数据库中。这些都是插曲。子类型也被插入。我知道这是因为 Add 方法将所有实体状态设置为已添加。我也知道我正在为每个请求使用一个新的上下文。我的数据访问位于一个单独的项目中,用于使用它进行持久性的服务。我知道我需要控制子类型的状态以告知上下文它们已经存在。我的问题是使用通用存储库模式这可能吗?有什么方法可以搜索所有子集合和 get/test 它们的状态。

感觉好像我应该更手动地做这件事,例如添加 TopType 而不设置任何导航属性,然后使用返回的 Id 在 InnerType 上设置外键,然后保存它创建必要的连接中的条目 table。

如果这是唯一明智的做法,那么 UnitOfWOrk 将不得不停止提供存储库并开始控制对存储库的访问 类。

任何关于已知变通方法的建议都会很棒。如果可以的话,我不想退回到命名存储库实现。

对我有用的答案是正确使用外键,而不是在添加相关实体时填充导航属性。当我移动到设置外键而不是导航 属性 时,一切都按预期工作。这是因为导航 属性 被视为新条目,即使它已经存在于数据库中。这是我的方法的产物,但对我来说似乎是一个足够公平的妥协。

在断开连接的存储库场景中,您可以使用重载的通用插入方法,子对象设置不变。例如

在 IGenericRepository 接口中

Task<T> Insert(T entity, object[] childEntities);

在通用存储库中

public async Task<T> Insert(T entity, object[] childEntities)
    {
        try
        {
            using (EntityContext_context = new EntityContext())
            {
                foreach (var item in childEntities)
                {
                    _context.Entry(item).State = EntityState.Unchanged;
                }

                _context.Entry(entity).State = EntityState.Added;
                await _context.SaveChangesAsync();

                return entity;
            }
        }
        catch (Exception)
        {

            throw;
        }
                        
    }

实际方法

 await _uow.EntityRepository.Insert(_ParentEntity, new object[] { _ParentEntity.ChildEntity1,_ParentEntity.ChildEntity2 });

当正确地将上述任务中的实体转换为对象数组时,不会复制父实体的子实体。