InvalidOperationException ที่ไม่คาดคิดเมื่อพยายามเปลี่ยนความสัมพันธ์ผ่านค่าเริ่มต้นของคุณสมบัติ


10

ในรหัสตัวอย่างด้านล่างฉันได้รับข้อยกเว้นต่อไปนี้เมื่อทำdb.Entry(a).Collection(x => x.S).IsModified = true:

System.InvalidOperationException: 'ไม่สามารถติดตามอินสแตนซ์ของประเภทเอนทิตี' B 'ได้เนื่องจากอินสแตนซ์อื่นที่มีค่าคีย์' {Id: 0} 'กำลังถูกติดตามอยู่ เมื่อแนบเอนทิตีที่มีอยู่ตรวจสอบให้แน่ใจว่าแนบอินสแตนซ์เอนทิตีเดียวที่มีค่าคีย์ที่กำหนดไว้แนบ

ทำไมมันไม่เพิ่มแทนที่จะแนบอินสแตนซ์ของ B

เอกสารสำหรับแปลกIsModifiedไม่ได้ระบุInvalidOperationExceptionว่าเป็นข้อยกเว้นที่เป็นไปได้ เอกสารไม่ถูกต้องหรือมีข้อบกพร่อง?

ฉันรู้ว่ารหัสนี้แปลก แต่ฉันเขียนมันเพื่อทำความเข้าใจว่า ef core ทำงานอย่างไรในบางกรณี egde สิ่งที่ฉันต้องการคือคำอธิบายไม่ใช่การแก้ไข

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    public class A
    {
        public int Id { get; set; }
        public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
    }

    public class B
    {
        public int Id { get; set; }
    }

    public class Db : DbContext {
        private const string connectionString = @"Server=(localdb)\mssqllocaldb;Database=Apa;Trusted_Connection=True";

        protected override void OnConfiguring(DbContextOptionsBuilder o)
        {
            o.UseSqlServer(connectionString);
            o.EnableSensitiveDataLogging();
        }

        protected override void OnModelCreating(ModelBuilder m)
        {
            m.Entity<A>();
            m.Entity<B>();
        }
    }

    static void Main(string[] args)
    {
        using (var db = new Db()) {
            db.Database.EnsureDeleted();
            db.Database.EnsureCreated();

            db.Add(new A { });
            db.SaveChanges();
        }

        using (var db = new Db()) {
            var a = db.Set<A>().Single();
            db.Entry(a).Collection(x => x.S).IsModified = true;
            db.SaveChanges();
        }
    }
}

A และ B สัมพันธ์กันอย่างไร หมายถึงคุณสมบัติความสัมพันธ์คืออะไร?
แซม

คำตอบ:


8

เหตุผลของข้อผิดพลาดในรหัสที่ให้ไว้มีดังนี้

เมื่อคุณได้รับการสร้างขึ้นนิติบุคคลAจากฐานข้อมูลสถานที่ให้บริการจะเริ่มต้นด้วยการเก็บที่มีสองระเบียนใหม่S ของแต่ละใหม่นี้หน่วยงานที่มีค่าเท่ากับBIdB0

// This line of code reads entity from the database
// and creates new instance of object A from it.
var a = db.Set<A>().Single();

// When new entity A is created its field S initialized
// by a collection that contains two new instances of entity B.
// Property Id of each of these two B entities is equal to 0.
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };

หลังจากเรียกใช้งานบรรทัดการvar a = db.Set<A>().Single()รวบรวมรหัสSของเอนทิตีAไม่มีเอนทิBตีจากฐานข้อมูลเนื่องจากDbContext Dbไม่ใช้การโหลดแบบสันหลังยาวและไม่มีการโหลดคอลเลกชันที่Sชัดเจน เอนทิตีAมีเอนทิตีใหม่Bที่สร้างขึ้นระหว่างการเริ่มต้นการรวบรวมSเท่านั้น

เมื่อคุณเรียกIsModifed = trueใช้Sเฟรมเวิร์กเอนทิตีของคอลเลกชันพยายามที่จะเพิ่มทั้งสองใหม่Bเข้ามาในการติดตามการเปลี่ยนแปลง แต่มันล้มเหลวเพราะBเอนทิตีใหม่ทั้งสองมีเหมือนId = 0กัน:

// This line tries to add to change tracking two new B entities with the same Id = 0.
// As a result it fails.
db.Entry(a).Collection(x => x.S).IsModified = true;

คุณสามารถเห็นได้จากการติดตามสแต็กที่เฟรมเวิร์กเอนทิตีพยายามเพิ่มBเอนทิตีลงในIdentityMap:

at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(Object relatedEntity, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.set_IsModified(Boolean value)

และข้อความแสดงข้อผิดพลาดยังบอกว่าไม่สามารถติดตามBเอนทิตีด้วยId = 0เพราะBเอนทิตีอื่นที่มีชื่อเดียวกันIdถูกติดตามอยู่แล้ว


วิธีแก้ไขปัญหานี้

ในการแก้ไขปัญหานี้คุณควรลบรหัสที่สร้างBเอนทิตีเมื่อเริ่มต้นการSรวบรวม:

public ICollection<B> S { get; set; } = new List<B>();

คุณควรเติมSชุดสะสมในสถานที่ที่Aสร้างขึ้นแทน ตัวอย่างเช่น:

db.Add(new A {S = {new B(), new B()}});

หากคุณไม่ใช้การโหลดแบบขี้เกียจคุณควรโหลดSคอลเล็กชันอย่างชัดเจนเพื่อเพิ่มรายการลงในการติดตามการเปลี่ยนแปลง:

// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;

ทำไมมันไม่เพิ่มแทนที่จะแนบอินสแตนซ์ของ B

ในระยะสั้นพวกเขาจะแนบ insted ของการเพิ่มเพราะพวกเขามีDetachedสถานะ

หลังจากรันโค้ดบรรทัด

var a = db.Set<A>().Single();

กรณีสร้างของกิจการมีสถานะB Detachedสามารถตรวจสอบได้โดยใช้รหัสถัดไป:

Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);

จากนั้นเมื่อคุณตั้งค่า

db.Entry(a).Collection(x => x.S).IsModified = true;

EF พยายามเพิ่มBเอนทิตีเพื่อเปลี่ยนการติดตาม จากซอร์สโค้ดของEFCoreคุณจะเห็นว่าสิ่งนี้นำเราไปสู่วิธีInternalEntityEntry.SetPropertyModifiedด้วยค่าอาร์กิวเมนต์ถัดไป:

  • property- หนึ่งในBหน่วยงานของเรา
  • changeState = true,
  • isModified = true,
  • isConceptualNull = false,
  • acceptChanges = true.

เมธอดนี้ที่มีอาร์กิวเมนต์ดังกล่าวจะเปลี่ยนสถานะของการเข้าDetached Bร่วมModifiedและพยายามเริ่มการติดตามสำหรับพวกเขา (ดูบรรทัดที่490 - 506) เนื่องจากBขณะนี้เอนทิตีมีสถานะModifiedสิ่งนี้ทำให้พวกเขาถูกแนบ (ไม่เพิ่ม)


คำตอบสำหรับ "เหตุใดจึงไม่เพิ่มแทนการแนบอินสแตนซ์ของ B" คุณกำลังพูดว่า "มันล้มเหลวเนื่องจากเอนทิตี B ใหม่ทั้งสองมี Id = 0 เหมือนกัน" ฉันคิดว่ามันผิดเพราะ ef core บันทึกทั้งสองด้วยรหัส 1 และ 2 ฉันไม่คิดว่ามันเป็นคำตอบที่ถูกต้องสำหรับคำถามนี้
DIlshod K

@DIlshod K ขอบคุณสำหรับความคิดเห็น ในส่วน "วิธีแก้ไขปัญหานี้" ฉันเขียนว่าSควรโหลดคอลเลกชันอย่างชัดเจนเนื่องจากรหัสที่ระบุไม่ใช้การโหลดแบบสันหลังยาว แน่นอนว่า EF บันทึกBเอนทิตีที่สร้างไว้ก่อนหน้านี้ในฐานข้อมูล แต่บรรทัดของโค้ดA a = db.Set<A>().Single()โหลดเอนทิตีที่Aไม่มีเอนทิตีในการรวบรวมSเท่านั้น ในการโหลดการโหลดคอลเลกชันSกระตือรือร้นควรใช้ ฉันจะเปลี่ยน asnwer ของฉันให้มีคำตอบสำหรับคำถามที่ชัดเจนว่า "ทำไมมันไม่เพิ่มเข้าไปแทนที่จะติดอินสแตนซ์ของ B"
Iliar Turdushev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.