How to update a record with dependency object with Entity Framework Core?

Enrico Rossini 196 Reputation points
2024-01-05T14:37:58+00:00

I'm studying Entity Framework Core version 8 using a Blazor project. The scenario is quite basic: I like to create a simple blog. I have an Article object that has 2 ICollection properties: Category and Tags

[Table("Articles")]
public class Article : Table
{
    [JsonPropertyName("body")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Body { get; set; }

    public ICollection<Category>? Categories { get; set; }
    public ICollection<Tag>? Tags { get; set; }
}

Then, the class Category is defined like the following (Tag is similar)

[Table("tbl_Categories")]
public class Category : Table
{
    public string? Name { get; set; }
    public ICollection<Article>? Articles { get; set; }
}

In the database, I have 3 tables: Articles, Categories and EF generates a linked table called ArticleCategory. This table is a link between the 2 main tables, but I don't have a representation in my code.

The issue starts when I want to update the Article object. If I pass the existing values for Category, I get this error

An error occurred while saving the entity changes. See the inner exception for details. ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_ArticleCategory'. Cannot insert duplicate key in object 'ArticleCategory'. The duplicate key value is (8, 15).

So, I think I have to delete this table first. I wrote this code to delete the records in the link table and then call for the update.

public void DeleteCategoriesByArticle(long id)
{
    var article = localDb?.Articles?
                          .Include(a => a.Categories)?
                          .Where(a => a.ID == id)
                          .FirstOrDefault();
    if (article != null)
    {
        bool hasChanges = false;

        if (article.Categories != null && article.Categories.Count() > 0)
        {
            article.Categories?.Clear();
            hasChanges = true;
        }

        if (hasChanges)
            localDb?.SaveChanges();
    }
}

After that, I call the function for the update. In this case I get another error

The instance of entity type 'Article' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

After a quick search, I changed how I retrieved the record

var article = localDb?.Articles?
                      .AsNoTracking()
                      .Include(a => a.Categories)?
                      .Where(a => a.ID == id)
                      .FirstOrDefault();

Now, I don't get the Violation of PRIMARY KEY error as before. I have created a generic repository and the Update function is like that

public void Update(T entity)
{
    _log.LogDebug($"[{nameof(T)}] Update an entity");

        
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

    if (entity.ModifiedAt == DateTime.MinValue)
        entity.ModifiedAt = DateTime.UtcNow;

    _db.Entry(entity).State = EntityState.Modified;
    _db.SaveChanges();
}

How can I update the Article record then?

Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
751 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Wenbin Geng 726 Reputation points Microsoft Vendor
    2024-01-08T08:36:13.39+00:00

    Hi @Enrico Rossini , Welcome to Microsoft Q&A.

    When you retrieve an article to update, make sure to decontextualize it as it may have been tracked. To do this, use "AsNoTracking()".

    var article = localDb?.Articles
                              .AsNoTracking()
                              .Include(a => a.Categories)
                              .Include(a => a.Tags)
                              .FirstOrDefault(a => a.ID == id);
    

    Then clear existing relationships and add new ones.

        if (article != null)
        {
            article.Categories?.Clear();
            article.Tags?.Clear();
            article.Categories = newCategories;
            article.Tags = newTags;
        }
    

    Next, after updating the detached entity with the new relationship, reattach it to the context and mark it as modified.

        if (article != null)
        {
            _db.Attach(article);
            _db.Entry(article).State = EntityState.Modified;
            _db.SaveChanges();
        }
    
    
    

    Best Regards,

    Wenbin


    If the answer is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.