Quantcast
Channel: Entity Framework
Viewing all articles
Browse latest Browse all 10318

Commented Issue: Inconsistent state when model has 1-many FK navigation on entity with composite key. [892]

$
0
0
When model has 1-many FK navigation and composite key we get into invalid state and fail on insert with non-obvious exception:

Violation of PRIMARY KEY constraint 'PK_dbo.ArubaTasks'. Cannot insert duplicate key in object 'dbo.ArubaTasks'.

Consider the following model:

public class ArubaRun
{
public int Id { get; set; }
public string Name { get; set; }
public int Purpose { get; set; }
public ICollection<ArubaTask> Tasks { get; set; }
}

public class ArubaTask
{
public int Id { get; set; }
public string Name { get; set; }
public bool Deleted { get; set; }
}

public class ArubaContext : DbContext
{
static ArubaContext()
{
Database.SetInitializer(new ArubaInitializer());
}

public DbSet<ArubaRun> Runs { get; set; }
public DbSet<ArubaTask> Tasks { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// composite key, non-integer key
modelBuilder.Entity<ArubaTask>().HasKey(k => new { k.Id, k.Name });

// need to map key explicitly, otherwise we get into invalid state
//modelBuilder.Entity<ArubaRun>().HasMany(r => r.Tasks).WithRequired().Map(m => { });
}
}

public class ArubaInitializer : DropCreateDatabaseIfModelChanges<ArubaContext>
{
private const int EntitiesCount = 10;

protected override void Seed(ArubaContext context)
{
var runs = InitializeRuns();
var tasks = InitializeTasks();


for (var i = 0; i < EntitiesCount; i++)
{
for (var j = 0; j < 3; j++)
{
runs[i].Tasks.Add(tasks[(i + j) % EntitiesCount]);
}
}

for (int i = 0; i < EntitiesCount; i++)
{
context.Runs.Add(runs[i]);
context.Tasks.Add(tasks[i]);
}

context.SaveChanges();

base.Seed(context);
}

private ArubaRun[] InitializeRuns()
{
var runs = new ArubaRun[EntitiesCount];
for (var i = 0; i < EntitiesCount; i++)
{
var run = new ArubaRun
{
Id = i,
Name = "Run Name" + i,
Purpose = i + 10,
Tasks = new List<ArubaTask>(),
};
runs[i] = run;
}

return runs;
}

private ArubaTask[] InitializeTasks()
{
var tasks = new ArubaTask[EntitiesCount];
for (int i = 0; i < EntitiesCount; i++)
{
var task = new ArubaTask
{
Id = i / 2,
Name = i % 2 == 0 ? "Foo" : "Bar",
Deleted = i % 3 == 0,
};
tasks[i] = task;
}

return tasks;
}
}


When we create database for it, we (incorrectly) pick Id par of Task key as a FK (see attached picture for details). We should have created a new FK column instead.


Stack trace:
System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.Execute(System.Collections.Generic.Dictionary<int,object> identifierValues, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.Data.Entity.Core.Mapping.Update.Internal.PropagatorResult,object>> generatedValues, System.Data.Entity.Internal.IDbCommandInterceptor commandInterceptor)

System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()

System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update.AnonymousMethod__2(System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator ut)

System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update<int>(System.Data.Entity.Core.IEntityStateManager entityCache, int noChangesResult, System.Func<System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator,int> updateFunction, bool throwOnClosedConnection)

System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update(System.Data.Entity.Core.IEntityStateManager entityCache, bool throwOnClosedConnection)

System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore.AnonymousMethod__20()

System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction<int>(System.Func<int> func, bool throwOnExistingTransaction, bool startLocalTransaction)

System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(System.Data.Entity.Core.Objects.SaveOptions options, bool throwOnExistingTransaction)

System.Data.Entity.Core.Objects.ObjectContext.SaveChanges.AnonymousMethod__12()

System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.ProtectedExecute<int>(System.Func<int> func)

System.Data.Entity.Infrastructure.ExecutionStrategy.Execute<int>(System.Func<int> func)

System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(System.Data.Entity.Core.Objects.SaveOptions options)

System.Data.Entity.Internal.InternalContext.SaveChanges()

System.Data.Entity.Internal.LazyInternalContext.SaveChanges()

System.Data.Entity.DbContext.SaveChanges()

Comments: Discussed with Andrew and we agree that we should fix this. We generally exclude 'Id' from being picked up by the FK detection convention (it would otherwise get picked up because a property in the dependent type that with the same name as the primary key property in the principal type is counted as a match - such as BlogId in the Post entity). It's odd if we suddenly start matching it just because you configure a composite key. Moving to Unassigned release to be re-triaged for EF6.

Viewing all articles
Browse latest Browse all 10318

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>