Slauma
Slauma

Reputation: 177163

Creating relationship in model with composite keys throws "Property is part of key and can't be modified" exception

I have the following problem in a project with EF 4.2. I made a small test project with EF 5.0 (on .NET 4.0) to check if the problem is the same in the newer version - and it is.

Sample model:

public class Order
{
    public int TenantId { get; set; }
    public int OrderId { get; set; }
    public string Name { get; set; }

    public int? CustomerId { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public int TenantId { get; set; }
    public int CustomerId { get; set; }
    public string Name { get; set; }
}

Context and mapping with Fluent API:

public class MyContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<Customer> Customers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>()
            .HasKey(o => new { o.TenantId, o.OrderId });

        modelBuilder.Entity<Customer>()
            .HasKey(c => new { c.TenantId, c.CustomerId });

        modelBuilder.Entity<Order>()
            .HasOptional(o => o.Customer)
            .WithMany()
            .HasForeignKey(o => new { o.TenantId, o.CustomerId });
    }
}

The important part here is that Order references (optionally) a Customer with a composite foreign key (TenantId, CustomerId) where the first part TenantId is part of the primary key (TenantId, OrderId) at the same time.

I create the database and one Order...

Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var ctx = new MyContext())
{
    var order = new Order { TenantId = 1, OrderId = 500, Name = "Test Order" };
    ctx.Orders.Add(order);
    ctx.SaveChanges();
}

...and it works. The database schema looks as expected (a foreign key relationship with principal Customer and principal key (TenantId, CustomerId) and dependent Order and foreign key (TenantId, CustomerId)).

Then I load the Order, create a new Customer, assign it to this Order and save the changes to update the relationship between Customer and Order:

using (var ctx = new MyContext())
{
    var order = ctx.Orders.Find(1, 500);
    var customer = new Customer { TenantId = 1, CustomerId = 1000,
                                  Name = "Test Customer" };

    order.Customer = customer;
    // order.TenantId is 1 and customer.TenantId is 1

    try
    {
        ctx.SaveChanges();
    }
    catch (Exception e)
    {
        throw;
    }
}

When calling SaveChanges I get an InvalidOperationException exception:

The property 'TenantId' is part of the object's key information and cannot be modified.

Stacktrace is:

at System.Data.Objects.EntityEntry.VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, Int32 ordinal, String memberName)
at System.Data.Objects.EntityEntry.GetAndValidateChangeMemberInfo(String entityMemberName, Object complexObject, String complexObjectMemberName, StateManagerTypeMetadata& typeMetadata, String& changingMemberName, Object& changingObject)
at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName, Object complexObject, String complexObjectMemberName)
at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName)
at System.Data.Objects.ObjectStateEntry.System.Data.Objects.DataClasses.IEntityChangeTracker.EntityMemberChanging(String entityMemberName)
at System.Data.Objects.Internal.SnapshotChangeTrackingStrategy.SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, Int32 ordinal, Object target, Object value)
at System.Data.Objects.Internal.EntityWrapper`1.SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, Int32 ordinal, Object target, Object value)
at System.Data.Objects.DataClasses.EntityReference.UpdateForeignKeyValues(IEntityWrapper dependentEntity, IEntityWrapper principalEntity, Dictionary`2 changedFKs, Boolean forceChange)
at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
at System.Data.Objects.ObjectStateManager.PerformAdd(IEntityWrapper wrappedOwner, RelatedEnd relatedEnd, IEntityWrapper entityToAdd, Boolean isForeignKeyChange)
at System.Data.Objects.ObjectStateManager.PerformAdd(IList`1 entries)
at System.Data.Objects.ObjectStateManager.DetectChanges()
at System.Data.Objects.ObjectContext.DetectChanges()
at System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean force)
at System.Data.Entity.Internal.InternalContext.GetStateEntries(Func`2 predicate)
at System.Data.Entity.Internal.InternalContext.GetStateEntries()
at System.Data.Entity.Infrastructure.DbChangeTracker.Entries()
at System.Data.Entity.DbContext.GetValidationErrors()
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()

I don't see where I would change the "part of the key" TenantId. Its value is 1 in Order and is 1 in Customer.

How can a modification of this key property happen with the code above and how can I get the update working?

Upvotes: 3

Views: 645

Answers (2)

Slauma
Slauma

Reputation: 177163

According to @Pawel's suggestion I've reported the problem as possible bug at Codeplex:

http://entityframework.codeplex.com/workitem/955

At the moment for the model in the question setting the foreign key properties directly instead of setting the navigation property works and doesn't throw an exception:

using (var ctx = new MyContext())
{
    var order = ctx.Orders.Find(1, 500);
    var customer = new Customer { TenantId = 1, CustomerId = 1000,
                                  Name = "Test Customer" };

    ctx.Customers.Add(customer);
    order.CustomerId = customer.CustomerId;

    try
    {
        ctx.SaveChanges();
        // No exception here
    }
    catch (Exception e)
    {
        throw;
    }
}

Upvotes: 1

Pawel
Pawel

Reputation: 31620

I briefly look at it and seems like a bug where EF "thinks" the TenantId on the Order changes while in fact it does not (i.e. values are the same). Do you mind filing a bug for this on http://entityframework.codeplex.com/?

Upvotes: 1

Related Questions