这个问题已经在stackoverflow上讨论了几次但是我找不到有关如何使用通用存储库模式解决它的答案。 给出的所有答案都直接使用DBContext。在通用存储库模式中,我将不能直接访问DBContext,也可以使用Unity for IOC。
所以这是问题:我有父母和父母有子集合。我在父级上设置了一些属性,并从集合中删除了子级。但是当我打电话给SaveChanges()
时我会收到错误
操作失败:无法更改关系,因为 一个或多个外键属性是不可为空的。当一个 改变了关系,相关的外键属性是 设置为空值。如果外键不支持空值, 必须定义新的关系,外键属性必须是 分配了另一个非空值,或者不相关的对象必须是 删除。
现在我不知道为什么EF试图将FK设置为null而不是仅删除记录。什么是将FK设置为null但保留DB中的孤立记录的目的。
如何使用存储库模式解决此问题?我是否需要从存储库中公开任何新方法?
实体
public class parent
{
public int ParentID {get;set;} //Primary Key
public string ParentName {get;set}
public ICollection<Child> Children {get;set}
}
public class Child
{
public int ChildID {get;set;} //Primary Key
public string ChildName {get;set;}
public int ParentID {get;set;} //Foreign Key
}
服务
public class MyService
{
private IGenericRepository _repository;
public MyService(IGenericRepository repository)
{
_repository = repository;
}
public void UpdateParent(int parentID,string parentName, int[] sourceChildIDs)
{
var p = _repository.GetQuery<Parent>()
.Include(x => x.Children)
.Where(x => x.ParentID == parentID)
.SingleOrDefault();
p.ParentName = parentName;
var childrenToDetete = new List<Child>();
foreach (var child in p.Children)
{
if (!sourceChildIDs.Contains(child.ChildID))
{
childrenToDetete.Add(child);
}
}
foreach (var child in childrenToDetete)
{
p.Children.Remove(child);
}
_repository.SaveChanges(); // i get error here
}
}
存储库
public class GenericRepository : IGenericRepository
{
private DbContext _dbContext;
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
{
throw new ArgumentNullException("dbContext");
}
_dbContext = dbContext;
}
public TEntity Create<TEntity>() where TEntity : class
{
return _dbContext.Set<TEntity>().Create<TEntity>();
}
public TEntity Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
return _dbContext.Set<TEntity>().Add(entity);
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
return _dbContext.Set<TEntity>();
}
public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().Where(predicate);
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_dbContext.Set<TEntity>().Remove(entity);
}
public void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
IEnumerable<TEntity> records = GetQuery<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public void Update<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_dbContext.Entry(entity).State = EntityState.Modified;
}
public int SaveChanges()
{
return _dbContext.SaveChanges();
}
}
答案 0 :(得分:0)
您要从父级中删除子级,而不是从数据库中删除子级。作为旁注,您可以更简洁的方式执行此操作:
foreach (var child in p.Children
.Where(child => !sourceChildIDs.Contains(child.ChildID))
.ToList())
{
p.Children.Remove(child);
}
但这只会打破父母与子女之间的联系。 EF会谨慎行事,并假设您只想删除孩子的外键参考,而不是完全删除孩子。
因此,您必须通过替换
之前的语句来从数据库中删除子项var delIds = p.Children.Where(child => !sourceChildIDs.Contains(child.ChildID))
.Select(c => c.ChildID).ToList();
_repository.Delete<Child>(c => delIds.Contains(c.ChildID));
顺便说一句,这是一个相当罕见的通用存储库实现。通常,通用存储库是针对一种类型实例化的,即定义将是GenericRepository<T>
。这些存储库的实例通常共享一个上下文实例,而它们在一个工作单元中协作,同时保存更改。