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

时间:2015-01-27 23:26:52

标签: c# entity-framework generics repository-pattern

在添加实体时,我遇到了使用Generic Repository和Entity Framework的问题。这是repo界面:

 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与其他表没有任何关系,那么这很好。但是请注意以下几点:

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

我在db中已经有了一些子类型。这些将返回给Web客户端。 Web客户端的用户创建新的TopType,然后添加尽可能多的InnerType。对于每个内部类型,他们可以选择已存在于数据库中并具有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方法设置要添加的所有实体状态。我也知道我正在为每个请求使用新的上下文。我的数据访问位于服务的单独项目中,该服务使用它来保持持久性。我知道我需要控制子类型的状态来告诉它们已经存在的上下文。我的问题是使用Generic Repository模式这可能吗?是否有某种方法可以搜索所有子集合并获取/测试其状态。

感觉好像我应该更多地做一些manaually,例如在没有设置任何导航属性的情况下添加TopType,然后使用返回的Id在InnerType上设置外键,然后保存它以创建必要的条目连接表。

如果这是唯一合理的方法,那么UnitOfWOrk将不得不停止提供回购并开始控制对Reposiotry类的访问。

关于已知工作的任何建议都会很棒。如果我可以帮助它,我不想退回到命名的存储库实现。

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

在断开连接的存储库方案中,可以使用重载的泛型插入方法,并将子对象设置为不变。例如

在IGenericRepository接口中

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

在GenericRepository中

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

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