Entity Framework: จะลบลูกโดยใช้รูปแบบพื้นที่เก็บข้อมูลทั่วไปได้อย่างไร

ปัญหานี้มีการพูดคุยกันหลายครั้งใน stackoverflow แต่ฉันไม่พบคำตอบเกี่ยวกับวิธีการแก้ไขโดยใช้รูปแบบพื้นที่เก็บข้อมูลทั่วไป คำตอบทั้งหมดที่ให้มากำลังใช้ DBContext โดยตรง ในรูปแบบพื้นที่เก็บข้อมูลทั่วไป ฉันจะไม่สามารถเข้าถึง DBContext ได้โดยตรง และฉันใช้ Unity สำหรับ IOC ด้วย

นี่คือปัญหา: ฉันมีผู้ปกครองและผู้ปกครองมีคอลเลกชันลูก ฉันกำลังตั้งค่าคุณสมบัติบางอย่างบนพาเรนต์และลบลูกออกจากคอลเลกชันด้วย อย่างไรก็ตาม เมื่อฉันโทร SaveChanges() ฉันได้รับข้อผิดพลาด

การดำเนินการล้มเหลว: ไม่สามารถเปลี่ยนแปลงความสัมพันธ์ได้เนื่องจากคุณสมบัติคีย์ต่างประเทศอย่างน้อยหนึ่งรายการไม่เป็นค่าว่าง เมื่อมีการเปลี่ยนแปลงความสัมพันธ์ คุณสมบัติคีย์ต่างประเทศที่เกี่ยวข้องจะถูกตั้งค่าเป็นค่าว่าง ถ้า Foreign-Key ไม่รองรับค่า Null จะต้องกำหนดความสัมพันธ์ใหม่ คุณสมบัติ Foreign-Key จะต้องได้รับการกำหนดค่าอื่นที่ไม่ใช่ Null หรือต้องลบออบเจ็กต์ที่ไม่เกี่ยวข้องออก

ตอนนี้ฉันไม่รู้ว่าเหตุใด EF จึงพยายามตั้งค่า FK เป็นโมฆะแทนที่จะลบบันทึก จุดประสงค์ของการตั้งค่า FK ให้เป็นโมฆะ แต่เก็บบันทึกเด็กกำพร้าไว้ในฐานข้อมูลคืออะไร

มีวิธีใดบ้างที่ฉันจะแก้ไขปัญหานี้โดยใช้รูปแบบพื้นที่เก็บข้อมูล ฉันจำเป็นต้องเปิดเผยวิธีการใหม่จากพื้นที่เก็บข้อมูลหรือไม่

เอนทิตี

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



คำตอบ (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<T> โดยทั่วไปอินสแตนซ์ของที่เก็บเหล่านี้จะใช้อินสแตนซ์บริบทเดียวร่วมกันในขณะที่ทำงานร่วมกันในหน่วยงานเดียวที่บันทึกการเปลี่ยนแปลงด้วย

person Gert Arnold    schedule 26.05.2016
comment
ฉันเดาว่าฉันยังคงต้องคำนวณเดลิด - person LP13; 27.05.2016
comment
ลืมไปหนึ่งบรรทัด ;) - person Gert Arnold; 27.05.2016
comment
ขอบคุณ .. หากฉันใช้ GenericRepository<T> สำหรับแต่ละประเภท คุณจะแทรก GenericRepository<T> ลงใน MyService ได้อย่างไร บริการนี้มีหลายวิธี และควรจะสามารถสืบค้นเอนทิตีใดๆ ได้ - person LP13; 27.05.2016
comment
จริงๆ แล้ว ฉันไม่เห็นประโยชน์ใดๆ ในที่เก็บข้อมูลทั่วไปนอกเหนือจากที่เก็บข้อมูลของ EF (DbSet) ดังนั้นฉันจึงไม่เคยใช้มันเลย แต่สามารถฉีดเข้าไปในคอนเทนเนอร์ IoC ที่เหมาะสมได้อย่างแน่นอน คุณควรจะสามารถค้นหาตัวอย่างสำหรับกรอบงาน DI ที่คุณเลือกได้ ถ้าไม่ โปรดถามคำถามใหม่ที่ครอบคลุมส่วนนั้นโดยเฉพาะ - person Gert Arnold; 27.05.2016
comment
ฉันคิดว่าการมีพื้นที่เก็บข้อมูลของคุณเองจะช่วยคุณในการทดสอบหน่วย ดังนั้นคุณจึงสามารถผ่านพื้นที่เก็บข้อมูลจำลองได้ และในกรณีที่คุณเปลี่ยนแหล่งข้อมูล คุณเพียงแค่ต้องเสียบพื้นที่เก็บข้อมูลใหม่โดยไม่ต้องเปลี่ยนตรรกะทางธุรกิจในชั้นบริการ - person LP13; 27.05.2016
comment
การเยาะเย้ยที่เก็บนั้นไม่ใช่เรื่องเล็กน้อย ฉันคิดว่าเมื่อคุณพูดว่า เปลี่ยนแหล่งข้อมูล คุณหมายถึงการแทนที่ชั้นการเข้าถึงข้อมูล เช่น โดย NHibernate นั่นก็ไม่ใช่เรื่องง่ายอย่างที่คิดเช่นกัน เนื่องจาก นามธรรมที่รั่วไหล - person Gert Arnold; 29.05.2016