1

This issue has been discussed several time on stackoverflow however i couldn't find answer about how it has been solved using generic repository pattern. All the answers given are using DBContext directly. In generic repository pattern i will not have direct access to DBContext, also im using Unity for IOC.

So here is the issue: I have parent and parent has child collection. I am setting some properties on parent and also delete child from the collection. However when i call SaveChanges() i get error

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

Now i dont know why EF is trying to set FK to null instead of just deleting the record. Whats the pupose of setting the FK to null but keeping the orphan record in DB.

Any way how do i solve this issue using repository pattern? Do i need to expose any new method from repository?

Entities

    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
    }

Service

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

Repository

    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();
        }       
    }
LP13
  • 30,567
  • 53
  • 217
  • 400

1 Answers1

0

You're removing the children from the Parent, but not from the database. As a side note, you can do this in a more concise way:

foreach (var child in p.Children
                       .Where(child => !sourceChildIDs.Contains(child.ChildID))
                       .ToList())
{
    p.Children.Remove(child);
}

But this only breaks the association between parent and children. EF errs on the side of caution and assumes that you want to remove the children's foreign key reference only, not remove the children altogether.

So you have to remove the children from the database by replacing the previous statements by

var delIds = p.Children.Where(child => !sourceChildIDs.Contains(child.ChildID))
                       .Select(c => c.ChildID).ToList();
_repository.Delete<Child>(c => delIds.Contains(c.ChildID));

By the way, this is a rather uncommon generic repository implementation. Usually, generic repositories are instantiated for one type, i.e. the definition would be GenericRepository<T>. Instances of these repositories typically share one context instance while they cooperate in one unit of work that also saves the changes.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • I guess I still have to calculate delid – LP13 May 26 '16 at 22:19
  • Forgot one line ;) – Gert Arnold May 26 '16 at 22:20
  • Thanks.. If i implement `GenericRepository` for each type, how are you going to inject `GenericRepository` into `MyService`. This service has several methods and should be able to query any entity. – LP13 May 27 '16 at 03:47
  • Actually, I don't see any benefit in generic repositories on top of EF's repositories (DbSet), so I never use them. But they can certainly be injected by any decent IoC container. You should be able to find examples for the DI framework of your choice. If not, please ask a new question specifically covering that part. – Gert Arnold May 27 '16 at 06:08
  • i think having your own repository will help you in unit testing, so you can pass mock repository. And also In case you change data source then you only need to plug in new repository without changing business logic in the service layer. – LP13 May 27 '16 at 15:05
  • Mocking repositories is [not trivial](http://stackoverflow.com/a/13352779/861716). I assume that when you say *change data source* you mean replacing the data access layer, for instance by NHibernate. That too is not as easy as it looks because of [leaky abstractions](http://codereview.stackexchange.com/a/126355/7251). – Gert Arnold May 28 '16 at 22:23