Kerangka Entitas: Bagaimana cara menghapus anak menggunakan pola repositori generik?

Masalah ini telah dibahas beberapa kali di stackoverflow namun saya tidak dapat menemukan jawaban tentang cara mengatasinya menggunakan pola repositori umum. Semua jawaban yang diberikan menggunakan DBContext secara langsung. Dalam pola repositori umum saya tidak akan memiliki akses langsung ke DBContext, saya juga menggunakan Unity untuk IOC.

Jadi inilah masalahnya: Saya punya orang tua dan orang tua punya koleksi anak. Saya mengatur beberapa properti pada induk dan juga menghapus anak dari koleksi. Namun ketika saya menelepon SaveChanges() saya mendapatkan kesalahan

Operasi gagal: Hubungan tidak dapat diubah karena satu atau lebih properti kunci asing tidak dapat dibatalkan. Ketika perubahan dilakukan pada suatu hubungan, properti kunci asing terkait diatur ke nilai nol. Jika kunci asing tidak mendukung nilai null, hubungan baru harus didefinisikan, properti kunci asing harus diberi nilai lain yang bukan nol, atau objek yang tidak terkait harus dihapus.

Sekarang saya tidak tahu mengapa EF mencoba menyetel FK ke null alih-alih hanya menghapus catatan. Apa tujuan menyetel FK ke nol tetapi menyimpan catatan yatim piatu di DB.

Bagaimana cara mengatasi masalah ini menggunakan pola repositori? Apakah saya perlu mengekspos metode baru dari repositori?

Entitas

    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
    }

Layanan

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

Repositori

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

person LP13    schedule 26.05.2016    source sumber


Jawaban (1)


Anda menghapus anak-anak dari Induk, tetapi tidak dari database. Sebagai catatan tambahan, Anda dapat melakukannya dengan cara yang lebih ringkas:

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

Namun hal ini hanya merusak pergaulan antara orang tua dan anak. EF melakukan kesalahan karena berhati-hati dan berasumsi bahwa Anda ingin menghapus referensi kunci asing turunan saja, bukan menghapus turunan seluruhnya.

Jadi, Anda harus menghapus anak-anak dari database dengan mengganti pernyataan sebelumnya dengan

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

Omong-omong, ini adalah implementasi repositori umum yang agak jarang. Biasanya, repositori generik dipakai untuk satu jenis, yaitu definisinya adalah GenericRepository<T>. Contoh dari repositori ini biasanya berbagi satu contoh konteks sementara mereka bekerja sama dalam satu unit kerja yang juga menyimpan perubahan.

person Gert Arnold    schedule 26.05.2016
comment
Saya kira saya masih harus menghitung delid - person LP13; 27.05.2016
comment
Lupa satu baris;) - person Gert Arnold; 27.05.2016
comment
Terima kasih.. Jika saya menerapkan GenericRepository<T> untuk setiap jenis, bagaimana Anda akan memasukkan GenericRepository<T> ke MyService. Layanan ini memiliki beberapa metode dan harus dapat menanyakan entitas apa pun. - person LP13; 27.05.2016
comment
Sebenarnya, saya tidak melihat manfaat apa pun dalam repositori generik selain repositori EF (DbSet), jadi saya tidak pernah menggunakannya. Tapi mereka pasti bisa disuntikkan oleh wadah IoC mana pun yang layak. Anda harus dapat menemukan contoh kerangka DI pilihan Anda. Jika tidak, silakan ajukan pertanyaan baru yang khusus mencakup bagian tersebut. - person Gert Arnold; 27.05.2016
comment
Saya pikir memiliki repositori sendiri akan membantu Anda dalam pengujian unit, sehingga Anda dapat melewati repositori tiruan. Dan juga jika Anda mengubah sumber data maka Anda hanya perlu memasang repositori baru tanpa mengubah logika bisnis di lapisan layanan. - person LP13; 27.05.2016
comment
Repositori yang mengejek adalah bukan hal sepele. Saya berasumsi bahwa ketika Anda mengatakan ubah sumber data yang Anda maksud adalah mengganti lapisan akses data, misalnya dengan NHibernate. Itu juga tidak semudah kelihatannya karena abstraksi yang bocor. - person Gert Arnold; 29.05.2016