Reputation: 121
I have two entities Employee
and Asset
. They are defined as:
public class Employee
{
public Employee()
{
this.Assets = new List<Asset>();
}
public int EmployeeID { get; set; }
public string Name { get; set; }
public virtual List<Asset> Assets { get; set; }
}
public class Asset
{
public string FacilityAssetID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Fluent defines the schema as:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>().ToTable("Employees");
modelBuilder.Entity<Employee>().HasKey(e => e.EmployeeID);
modelBuilder.Entity<Employee>().Property(e => e.Name).HasMaxLength(50).IsRequired();
modelBuilder.Entity<Employee>().HasMany(e => e.Assets).WithMany().Map(m => m.MapLeftKey("EmployeeID").MapRightKey("FacilityAssetID").ToTable("EmployeeToAsset"));
modelBuilder.Entity<Asset>().ToTable("Assets");
modelBuilder.Entity<Asset>().HasKey(a => a.FacilityAssetID);
modelBuilder.Entity<Asset>().Property(a => a.Name).HasMaxLength(50).IsRequired();
modelBuilder.Entity<Asset>().Property(a => a.Description).HasMaxLength(200).IsRequired();
base.OnModelCreating(modelBuilder);
}
Now, I have some employees in the tables as well as some assets. The assets are populated using a web service. Suppose I have the following assets:
There are two assets with IDs: Asset1F32 and Asset1C53. If I try to add an asset to an employee using the following code, I can successfully add an asset:
using (VCContext context = new VCContext())
{
Employee emp = context.Employees.First();
Asset assetDetached = new Asset() { FacilityAssetID = "Asset1F32" };
context.Assets.Attach(assetDetached);
emp.Assets.Add(assetDetached);
context.SaveChanges();
}
If I loop through some of the assets beforehand and try to add an asset, it will throw an exception. Here's that code:
using (VCContext context = new VCContext())
{
foreach (Employee employee in context.Employees)
{
Console.WriteLine(employee.Name);
foreach (Asset asset in employee.Assets)
{
Console.WriteLine(string.Format(" - {0}", asset.FacilityAssetID));
}
}
Employee emp = context.Employees.First();
Asset assetDetached = new Asset() { FacilityAssetID = "Asset1F32" };
context.Assets.Attach(assetDetached); // EXCEPTION HERE!!!
emp.Assets.Add(assetDetached);
context.SaveChanges();
}
The exception thrown is:
Attaching an entity of type 'ASTC.Asset' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
Why would it throw an exception in the second case but not the first case? Additionally, how can I add the detached asset in the second scenario?
Upvotes: 2
Views: 796
Reputation: 109205
By looping through context.Employees
and their assets, Asset
"Asset1F32" is loaded into the context, i.e. attached, so you can't attach another instance with the same primary key value.
I don't know why you loop through the assets, but a quick fix is to use context.Employees.AsNoTracking()
, so the entities from that statement won't be attached to the context.
A more robust fix would be:
var id = "Asset1F32";
var asset = context.Assets.Local.FirstOrDefault(a => FacilityAssetID == id)
?? new Asset() { FacilityAssetID = id };
emp.Assets.Add(asset);
By this you check in the context's cache if the asset already is attached and if it isn't, you create it as a stub entity, so you can establish the association efficiently (i.e. without unnecessarily fetching the asset from the database).
Upvotes: 2
Reputation: 528
See if this answer helps:
It shows how to check if the object is already attached.
This version works for me:
public static bool Exists<T>(T entity) where T : class
{
return dbContext.Set<T>().Local.Any(e => e == entity);
}
called like this:
if (!Exists<Person>(p))
{
dbContext.People.Attach(p);
}
Upvotes: 0