【问题标题】:Entity Framework: How to delete child using generic repository pattern?实体框架:如何使用通用存储库模式删除子项?
【发布时间】:2016-05-26 19:55:38
【问题描述】:

这个问题已经在 stackoverflow 上讨论过几次,但是我找不到关于如何使用通用存储库模式解决它的答案。 给出的所有答案都是直接使用 DBContext 。在通用存储库模式中,我将无法直接访问 DBContext,我也将 Unity 用于 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();
        }       
    }

【问题讨论】:

    标签: entity-framework entity-framework-6 repository-pattern


    【解决方案1】:

    您正在从父级中删除子级,而不是从数据库中。作为旁注,您可以以更简洁的方式执行此操作:

    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&lt;T&gt;。这些存储库的实例通常共享一个上下文实例,同时它们在一个也保存更改的工作单元中协作。

    【讨论】:

    • 我想我还是得计算熟食
    • 忘记了一行 ;)
    • 谢谢.. 如果我为每种类型实现GenericRepository&lt;T&gt;,你将如何将GenericRepository&lt;T&gt; 注入MyService。该服务有多种方法,应该能够查询任何实体。
    • 实际上,我没有看到 EF 存储库 (DbSet) 之上的通用存储库有任何好处,所以我从不使用它们。但它们当然可以被任何体面的 IoC 容器注入。您应该能够找到您选择的 DI 框架的示例。如果没有,请提出一个专门针对该部分的新问题。
    • 我认为拥有自己的存储库将有助于您进行单元测试,因此您可以通过模拟存储库。而且,如果您更改数据源,则只需插入新的存储库,而无需更改服务层中的业务逻辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    相关资源
    最近更新 更多