最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Error on adding entity with list of child objects that has foreign keys - Stack Overflow

programmeradmin2浏览0评论

Using Blazor server-side UI with Entity Framework Core.

I have a Student table. One student can have multiple tests assigned, each test has test subject type which is FK to another lookup table.

public class Student
{
    public int Id { get; set;}
    public virtual ICollection<TestDetail > TestDetails{ get; set; } = new List< TestDetail >();
}

public class TestDetail
{
     public int StudentId { get; set; }
     public virtual Student Student { get; set; } = null!;
     public int? SubjectTypeId { get; set; }
     public virtual SubjectType? SubjectType { get; set; } = null!;
}

public class TestDetailConfiguration
{
      entity.Property(e => e. SubjectTypeId).HasColumnName("SUBJECT_TYPE_CD");
      entity.HasOne(d => d. SubjectType).WithMany()
              .HasForeignKey(d => d.TestSubjectTypeId)
              .HasConstraintName("FK_NAME_TO_SUBJECT_TABLE");
       entity.HasOne(d => d.Student).WithMany(p => p. TestDetails)
            .HasForeignKey(d => d.StudentId)
            .OnDelete(DeleteBehavior.ClientCascade)
            .HasConstraintName("FK_T_...");
}

In the UI, user can add multiple tests to a student at one time. Subject can be picked from a dropdown which populated with list of subjects from the Subject table.

Clicking on the AddTest button creates an object of type TestDetail and SubjectType is assigned with the test subject.

Populate the dropdown by querying the data context from a service class where factory is injected

public List<SubjectType> GetSubjects()
{
    using (var _context = factory.CreateDbContext())
    {
        var rows = _context.Set<Subject>();
        return rows;
    }
}

The UI page has:

[Inject]
private IDbContextFactory<DBContext> factory { get; set; }
private DBContext _dbContext;
private IEnumerable<SubjectType> subjecteTypes = [];

private void Onintialized()
{
    _dbContext = factory.CreateDbContext();
    subjectTypes = Service.GetSubjects();
    Student student = new Student();
    _dbContext.Students.Add(student);
}

On add test button click, creates a TestDetail object and adds to the collection.

student.TestDetails.Add(new TestDetail());

Then later its SubjectType is populated with the selected subject from the dropdown with values "1 – English", "2 – Science".

When I add two tests with the same subject "1 - English", Entity Framework Core always throwing this exception on save:

The instance of entity type cannot be tracked because another instance of this type with the same key {1} is already being tracked.

I tried using .AsNoTracking() while getting the Subject list, but that didn't work either.

Thanks

Using Blazor server-side UI with Entity Framework Core.

I have a Student table. One student can have multiple tests assigned, each test has test subject type which is FK to another lookup table.

public class Student
{
    public int Id { get; set;}
    public virtual ICollection<TestDetail > TestDetails{ get; set; } = new List< TestDetail >();
}

public class TestDetail
{
     public int StudentId { get; set; }
     public virtual Student Student { get; set; } = null!;
     public int? SubjectTypeId { get; set; }
     public virtual SubjectType? SubjectType { get; set; } = null!;
}

public class TestDetailConfiguration
{
      entity.Property(e => e. SubjectTypeId).HasColumnName("SUBJECT_TYPE_CD");
      entity.HasOne(d => d. SubjectType).WithMany()
              .HasForeignKey(d => d.TestSubjectTypeId)
              .HasConstraintName("FK_NAME_TO_SUBJECT_TABLE");
       entity.HasOne(d => d.Student).WithMany(p => p. TestDetails)
            .HasForeignKey(d => d.StudentId)
            .OnDelete(DeleteBehavior.ClientCascade)
            .HasConstraintName("FK_T_...");
}

In the UI, user can add multiple tests to a student at one time. Subject can be picked from a dropdown which populated with list of subjects from the Subject table.

Clicking on the AddTest button creates an object of type TestDetail and SubjectType is assigned with the test subject.

Populate the dropdown by querying the data context from a service class where factory is injected

public List<SubjectType> GetSubjects()
{
    using (var _context = factory.CreateDbContext())
    {
        var rows = _context.Set<Subject>();
        return rows;
    }
}

The UI page has:

[Inject]
private IDbContextFactory<DBContext> factory { get; set; }
private DBContext _dbContext;
private IEnumerable<SubjectType> subjecteTypes = [];

private void Onintialized()
{
    _dbContext = factory.CreateDbContext();
    subjectTypes = Service.GetSubjects();
    Student student = new Student();
    _dbContext.Students.Add(student);
}

On add test button click, creates a TestDetail object and adds to the collection.

student.TestDetails.Add(new TestDetail());

Then later its SubjectType is populated with the selected subject from the dropdown with values "1 – English", "2 – Science".

When I add two tests with the same subject "1 - English", Entity Framework Core always throwing this exception on save:

The instance of entity type cannot be tracked because another instance of this type with the same key {1} is already being tracked.

I tried using .AsNoTracking() while getting the Subject list, but that didn't work either.

Thanks

Share Improve this question edited Mar 6 at 18:00 marc_s 757k184 gold badges1.4k silver badges1.5k bronze badges asked Mar 6 at 14:06 blue pinkblue pink 413 bronze badges 4
  • Please post the code as a minimal reproducible example. The current code is too sketchy and described code ("On add test button ...") is always ambiguous. – Gert Arnold Commented Mar 6 at 14:17
  • _dbContext = factory.CreateDbContext() - it is wrong. DbContext should be allocated and disposed within method, so no assigning in constructor. – Svyatoslav Danyliv Commented Mar 6 at 19:41
  • It's disposed when the UI page is unloading. – blue pink Commented Mar 6 at 22:29
  • Responding to the below comment: I started using Id field of look up entity for dropdowns but came across a limitation (probably not but trying to find more info) in MudBlazor's Select where when Ids used, typing alphabets not navigating to the item starts with the typed letter. So switched to entity binding. – blue pink Commented Mar 6 at 23:23
Add a comment  | 

1 Answer 1

Reset to default 1

TL;DR

Don't use Drop Down Lists to link entity object references, just use them as Id lookups and only assign the ForeignKey fields via drop downs.


.AsNoTracking() in this case further compounds your issues. If you are using a common context for the page life time, and you want to make changes back to any records that have 1:N links to reference tables, then you really want to enable tracking.

You want tracking so that EF knows that the selected SubjectType was already loaded from the database.

.AsNoTracking() is actually irrelevant here, your GetSubjects() is a different instance of the DbContext that gets disposed anyway, so it isn't being tracked anywhere, that is why it didn't make a difference.

The problem with this is how EF manages entity creation. student is a new record in your _dbContext but it has not yet been persisted to the database. When you link related entities to the student and EF isn't tracking those related entities in the DbContext that is executing the save then EF will try to attach them first if you have ForeignKey fields in the model.

The error you are seeing suggests key fields are in your model and you are using generated keys, that is a good start ;) but it also suggests that due to your code implementation, the same Subject entities that are linked to multiple records are not the same object reference. Either some level of cloning or serialization has occurred or the GetSubjects() has been called multiple times for the lifetime of the student record. EF does try to prevent this happening when the records are attached, but it's not foolproof

  • If you are not using ForeignKey fields, then EF assumes that the entire graph needs to be created, so the main student record and any child nodes in the object graph that haven't been tracked by the context will also be created.

You may be able to work around this by manually Attaching the selected Subject entity to the _dbContext and checking that all the other references to the same Subject are also attached and all have the tracking state to Unchanged but at that point it might have been easier for GetSubjects() to use the same _dbContext as the form (and not dispose it!)

  • usage of a common DbContext is generally regarded as an anti-pattern because it leads to inefficiencies and code that gets complicated to maintain. Even having a common _dbContext at the form level is just waiting for problems exactly like this. So I don't recommend it for commercial software, but it might be a simple workaround that you can accept for now.

A better design is to use expose your ForeignKey fields as properties in your model (if you are not already) and to set these ForeignKey fields from the dropdowns and not to assign the whole object. In this configuration you would load the GetSubjects() once, perhaps for the lifetime of the application, or you could cache and reuse the results. Then you bind the Key field from the drop down list selection to the student record.

  • This is generally the concept that dropdown lists were originally designed to support, long before EF
发布评论

评论列表(0)

  1. 暂无评论