Efcore: Support cascade updates for owned types/aggregates

Created on 13 Dec 2017  路  8Comments  路  Source: dotnet/efcore

When I attach an entity that has an owned type attached to it and I change the state of that entity using the ChangeTracker to EntityState.Added, the owned type should also get the EntityState.Added state, but it doesn't.

Exception message: The entity of 'Entity' is sharing the table 'Entity' with 'Entity.OwnedType#OwnedType', but there is no entity of this type with the same key value that has been marked as 'Added'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.'
Stack trace:    at Microsoft.EntityFrameworkCore.Update.Internal.ModificationCommandIdentityMap.Validate(Boolean sensitiveLoggingEnabled)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.CreateModificationCommands(IReadOnlyList`1 entries, Func`1 generateParameterName)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.<BatchCommands>d__8.MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(Tuple`2 parameters)
   at Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ExecutionStrategyExtensions.Execute[TState,TResult](IExecutionStrategy strategy, TState state, Func`2 operation)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at EfCoreState.Program.Main(String[] args) in c:\users\nickv\source\repos\projects\EfCoreState\EfCoreState\Program.cs:line 19

Steps to reproduce

```c#
class Program
{
static void Main(string[] args)
{
var entity = new Entity();
entity.OwnedType.SomeText = "Hello world!";

        var context = new TestContext();
        context.Database.EnsureCreated();

        context.Attach(entity);
        context.ChangeTracker.Entries<Entity>().First().State = EntityState.Added;
        context.SaveChanges();

        context.Database.EnsureDeleted();
    }
}


public class Entity
{
    public Entity()
    {
        Id = Guid.NewGuid();
        OwnedType = new OwnedType();
    }

    public Guid Id { get; set; }

    public OwnedType OwnedType { get; set; }
}

public class OwnedType
{
    public string SomeText { get; set; }
}

public class TestContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true");

        base.OnConfiguring(optionsBuilder);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var entity = modelBuilder.Entity<Entity>();
        entity.HasKey(e => e.Id);
        entity.OwnsOne(e => e.OwnedType);

        base.OnModelCreating(modelBuilder);
    }
}

```

Further technical details

EF Core version: 2.0.1
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10 (1703)
IDE: Visual Studio 2017 15.5.1

breaking-change type-enhancement

Most helpful comment

I'd +1 this but not specifically for owned types, but for one-to-one relationships. Having an UPDATE CASCADE constraint on the FK would be helpful, specially if you use direct SQL queries (you have to update the FK on the other table manually). Not sure if EF does update the FK (it probably does) but it definitely doesn't add a UPDATE CASCADE constraint.

Thing is, this seems to be supported on Migrations... I can manually add it on the ForeignKey method:

constraints: table =>
{
    table.PrimaryKey("PK_Products", x => x.Id);
    table.ForeignKey(
        name: "FK_Products_EanUpcs_EanUpcId",
        column: x => x.EanUpcId,
        principalSchema: "om",
        principalTable: "EanUpcs",
        principalColumn: "Id",
        onDelete: ReferentialAction.Restrict,
    onUpdate: ReferentialAction.Cascade // <-- i can add this manually
    );
});

But there doesn't seem to be a fluent API to configure it

All 8 comments

@nickverschueren We will discuss this, but a quick question: why does the code call Attach first and then change the state instead of calling Add?

@ajcvickers This is just to illustrate the problem. We actually use our own self-tracking entities.

@nickverschueren I am re-purposing this issue into a feature for cascade updates that would behave similar to cascade deletes in that it would allow the states of owned child entites to update based on the state of the parent in a similar way to cascade delete does now.

Wasn't this feature committed back in October? The discussion below seemed to indicated you had decided Owned types do follow the state of their owning entity.

https://github.com/aspnet/EntityFrameworkCore/issues/7985

How does the owned state follow? Does it follow if you make changes via the TrackGraph? Or just via a context Add(). BTW, I have been fighting Nick's error all day. I can't add an entity with an owned type. Is it me, or is the feature not quite fully working?

@FrederickBrier This issue is about cascade updates in the database. EF Core does not support this yet, which is why this issue is in the Backlog milestone. In general, as I said in another comment, without more details (i.e. code to reproduce what you are seeing) it is very hard to know exactly what you are asking and whether or not you are hitting a bug.

@ajcvickers I will put together an example, but it will probably be next week. For the time being, added the owned type's members to the owning entity, plus the owned type, tagged as NotMapped, with get/set members to write to individual persisted properties. Not great, but the tests pass, the database schema is the same, and we will figure out :). Oh, and thank you for the help. All your discussions on these issues is very helpful.

I'd +1 this but not specifically for owned types, but for one-to-one relationships. Having an UPDATE CASCADE constraint on the FK would be helpful, specially if you use direct SQL queries (you have to update the FK on the other table manually). Not sure if EF does update the FK (it probably does) but it definitely doesn't add a UPDATE CASCADE constraint.

Thing is, this seems to be supported on Migrations... I can manually add it on the ForeignKey method:

constraints: table =>
{
    table.PrimaryKey("PK_Products", x => x.Id);
    table.ForeignKey(
        name: "FK_Products_EanUpcs_EanUpcId",
        column: x => x.EanUpcId,
        principalSchema: "om",
        principalTable: "EanUpcs",
        principalColumn: "Id",
        onDelete: ReferentialAction.Restrict,
    onUpdate: ReferentialAction.Cascade // <-- i can add this manually
    );
});

But there doesn't seem to be a fluent API to configure it

+1

Was this page helpful?
0 / 5 - 0 ratings