0

I am trying to update a table and its related entities but the child entities are not updating. I don't know if the child entities should update automatically, or if I need to manually update them. Or I don't know if my EF configuration or database configuration are incorrect.

My tables are Template and TemplateFields. A template can have many templateFields but a templateField can only have one parent template. Here is my diagram.

And here are my table configurations in EF:

public class TemplateConfiguration : EntityConfiguration<Template>
{
    public override void Configure(EntityTypeBuilder<Template> builder)
    {
        base.Configure(builder);

        builder.HasMany(template => template.TemplateFields)
            .WithOne(field => field.ParentTemplate)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

public class TemplateFieldConfiguration : EntityConfiguration<TemplateField>
{
    public override void Configure(EntityTypeBuilder<TemplateField> builder)
    {
        base.Configure(builder);

        builder.HasOne(field => field.ParentTemplate)
            .WithMany(template => template.TemplateFields);
    }
}

Here's the Template model:

[Table("Template")]
public class Template : Entity
{
    [Required]
    public virtual string Name { get; set; }

    public virtual IList<TemplateField> TemplateFields { get; set; }
}

Here's the TemplateField model:

[Table("TemplateField")]
public class TemplateField : Entity
{
    [Required]
    public virtual Template ParentTemplate { get; set; }
    
    [Required]
    public virtual string Name { get; set; }

    [Required]
    public virtual DataType DataType { get; set; }

    [Required]
    public virtual bool IsSharedAcrossLanguages { get; set; }
}

Here is the Template update method:

    public virtual async Task<Template> EditAsync(Template template)
    {
        var originalItem = await _dbContext.Template.FindAsync(template.Id);
        _dbContext.Entry(originalItem).CurrentValues.SetValues(template);
        await _dbContext.SaveChangesAsync();

        var newTemplate = await _dbContext.Template.FindAsync(template.Id);

        return newTemplate;
    }

Here is some example JSON payload I've been testing with to add a template and associated fields:

{
    "name": "New Template",
    "fields": [
        {
            "name": "Title",
            "dataType": "text",
            "isSharedAcrossLanguages": false
        },
        {
            "name": "Content",
            "dataType": "image",
            "isSharedAcrossLanguages": true
        },
        {
            "name": "Header",
            "dataType": "text",
            "isSharedAcrossLanguages": false
        }
    ]
}

And last of all here is the payload for updating a template and its fields (with example Guids):

{
    "id": "32e2ff27-24e6-49d4-bea7-08d924513aa3",
    "name": "Edited Template",
    "fields": [
        {
            "id": "80072a3a-c717-4ab0-53ef-08d924513aae",
            "name": "Edited Title",
            "dataType": "text",
            "isSharedAcrossLanguages": false
        },
        {
            "id": "3865f8b1-0f3c-4edc-53f0-08d924513aae",
            "name": "Content",
            "dataType": "image",
            "isSharedAcrossLanguages": true
        },
        {
            "id": "57b5b7af-d265-4244-53f1-08d924513aae",
            "name": "Header",
            "dataType": "text",
            "isSharedAcrossLanguages": false
        }
    ]
}

What's confusing to me is the standard EF method AddAsync() will cascade adding child templateFields automatically, which tells me cascading updates should be possible. What I'm thinking is that '_dbContext.Entry(originalItem).CurrentValues.SetValues(template);' only gets values that have changed and since my Template table does not contain a foreign key column pointing to the TemplateField table, EF can't recognize it as a changed value.

Is that a correct assumption? If so, is there a way to get cascading updates to work?

  • When you load the template with `FindAsync()` try to call `Include(t => t.TemplateFields)` on the `DbSet` first. This should ensure they are actually loaded into memory. You might have to use `Single()` or `First()` instead of `FindAsync()` though. – Good Night Nerd Pride Jun 01 '21 at 15:42
  • I will try that out, thanks – James654987 Jun 01 '21 at 15:46

1 Answers1

0

I figured out that CurrentValues only includes properties that correspond to columns in the database. Which I think makes sense after thinking about it. When EF populates the model from the database it doesn't go deep enough to recognize the relationship with the TemplateFields model.

The TemplateFields property in my model is a reference/navigation property and does not have or need a corresponding column in the database. So because of this, EF doesn't recognize that the TemplateFields have any changes.

I found this SO post on the different ways of updating and method 1 will work just fine for my purposes. Just updating each field individually as follows:

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}