Không mong đợi UnlimitedOperationException khi cố gắng thay đổi mối quan hệ thông qua giá trị mặc định của thuộc tính


10

Trong mã mẫu dưới đây, tôi nhận được ngoại lệ sau khi thực hiện db.Entry(a).Collection(x => x.S).IsModified = true:

System.InvalidOperationException: 'Không thể theo dõi thể hiện của loại thực thể' B 'vì một phiên bản khác có giá trị khóa' {Id: 0} 'đã được theo dõi. Khi đính kèm các thực thể hiện có, đảm bảo rằng chỉ có một thực thể có giá trị khóa đã cho được đính kèm.

Tại sao nó không thêm thay vì đính kèm các thể hiện của B?

Kỳ lạ là tài liệu cho IsModifiedkhông chỉ định InvalidOperationExceptionlà một ngoại lệ có thể. Tài liệu không hợp lệ hoặc một lỗi?

Tôi biết mã này là lạ, nhưng tôi đã viết nó chỉ để hiểu làm thế nào lõi ef hoạt động trong một số trường hợp egde kỳ lạ. Những gì tôi muốn là một lời giải thích, không phải là một công việc xung quanh.

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 và B có liên quan như thế nào? có nghĩa là tài sản mối quan hệ là gì?
sam

Câu trả lời:


8

Lý do của lỗi trong mã được cung cấp như sau.

Khi bạn nhận được thực thể được tạo Atừ cơ sở dữ liệu, thuộc tính của nó Sđược khởi tạo với một bộ sưu tập chứa hai bản ghi mới B. Idcủa mỗi Bthực thể mới này là bằng 0.

// 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 {} };

Sau khi thực hiện dòng var a = db.Set<A>().Single()thu thập mã Scủa thực thể Akhông chứa Bthực thể từ cơ sở dữ liệu, vì DbContext Dbkhông sử dụng tải lười biếng và không có tải rõ ràng của bộ sưu tập S. Thực thể Achỉ chứa các Bthực thể mới được tạo trong quá trình khởi tạo bộ sưu tập S.

Khi bạn gọi IsModifed = truecho Skhung thực thể bộ sưu tập, hãy cố gắng thêm hai quyền mới đó Bvào theo dõi thay đổi. Nhưng nó thất bại vì cả hai Bthực thể mới đều giống nhau 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ạn có thể thấy từ theo dõi ngăn xếp mà khung thực thể cố gắng thêm Bcác thực thể vào 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)

Và thông báo lỗi cũng cho biết rằng nó không thể theo dõi Bthực thể Id = 0vì một Bthực thể khác có cùng thực thể Idđã được theo dõi.


Làm thế nào để giải quyết vấn đề này.

Để giải quyết vấn đề này, bạn nên xóa mã tạo Bcác thực thể khi khởi tạo Sbộ sưu tập:

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

Thay vào đó, bạn nên điền vào Sbộ sưu tập tại nơi Ađược tạo. Ví dụ:

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

Nếu bạn không sử dụng tải lười biếng, bạn nên tải Sbộ sưu tập một cách rõ ràng để thêm các mục của nó vào theo dõi thay đổi:

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

Tại sao nó không thêm thay vì đính kèm các thể hiện của B?

Nói tóm lại , chúng được đính kèm khi được thêm vào vì chúng có Detachedtrạng thái.

Sau khi thực hiện dòng mã

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

tạo ra các thực thể Bcó trạng thái Detached. Nó có thể được xác minh bằng cách sử dụng mã tiếp theo:

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

Sau đó khi bạn đặt

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

EF cố gắng thêm Bcác thực thể để thay đổi theo dõi. Từ mã nguồn của EFCore, bạn có thể thấy rằng điều này dẫn chúng ta đến phương thức InternalEntityEntry.SetPropertyModified với các giá trị đối số tiếp theo:

  • property- một trong những Bthực thể của chúng tôi ,
  • changeState = true,
  • isModified = true,
  • isConceptualNull = false,
  • acceptChanges = true.

Phương thức này với các đối số như vậy sẽ thay đổi trạng thái của các mục Detached Bnhập Modifiedvà sau đó cố gắng bắt đầu theo dõi chúng (xem các dòng 490 - 506). Bởi vì Bcác thực thể hiện có trạng thái Modifiednày dẫn đến chúng được đính kèm (không được thêm vào).


Câu trả lời cho "Tại sao nó không thêm thay vì đính kèm các phiên bản của B?" Bạn đang nói "thất bại vì cả hai thực thể B mới có cùng Id = 0". Tôi nghĩ đó là sai vì lõi ef lưu cả id và 1 id. Tôi không nghĩ đó là câu trả lời chính xác cho câu hỏi này
DIlshod K

@DIlshod K Cảm ơn bạn đã bình luận. Trong phần "Cách giải quyết vấn đề này" tôi đã viết rằng bộ sưu tập Snên được tải một cách rõ ràng, bởi vì mã được cung cấp không sử dụng tải lười biếng. Tất nhiên, EF đã lưu các Bthực thể được tạo trước đó trong cơ sở dữ liệu. Nhưng dòng mã A a = db.Set<A>().Single()chỉ tải thực thể Amà không có thực thể trong bộ sưu tập S. Để tải bộ sưu tập tải Sháo hức nên được sử dụng. Tôi sẽ thay đổi asnwer của mình để bao gồm một cách rõ ràng câu trả lời cho câu hỏi "Tại sao nó không thêm thay vì đính kèm các thể hiện của B?".
Iliar Turdushev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.