通用存储库添加重复的子实体
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 });
当正确地将上述任务中的实体转换为对象数组时,不会复制父实体的子实体。
我在将通用存储库与 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 });
当正确地将上述任务中的实体转换为对象数组时,不会复制父实体的子实体。