I have an edit model and an EF entity, and I want to use AutoMapper to update the entity with data from the edit model. This works very well:
// The classes involved
public class FooEditModel {
public Guid Id { get; set; }
public string FooProp { get; set; }
}
public class FooEntity {
public Guid Id { get; set; }
public string FooProp { get; set; }
}
// Mapper configuration
cfg.CreateMap<FooEditModel, FooEntity>();
// Actual usage
var entity = _dbContext.Foos
.Where(e => e.Id == id) // id is a route param
.Single();
_mapper.Map(editModel, entity); // editModel is populated by model binding
_dbContext.SaveChanges();
Now, entity has been updated with the value of FooProp from editModel. All is well.
However, when I add a collection of a different type, it doesn't work as well as I'd like anymore
// classes
pulic class FooEditModel {
public Guid Id { get; set; }
public IReadOnlyCollection<BarEditModel> Bars { get; set; }
}
public class BarEditModel {
public Guid Id { get; set; }
public string BarProp { get; set; }
}
public class FooEntity {
public Guid Id { get; set; }
public IReadOnlyCollection<BarEntity> Bars { get; set; }
}
public class BarEntity {
public string BarProp { get; set; }
}
// mapper config
cfg.CreateMap<FooEditModel, FooEntity>();
cfg.CreateMap<BarEditModel, BarEntity>();
// usage
var entity = _dbContext.Foos
.Include(f => f.Bars)
.Where(f => f.id == id)
.Single();
_mapper.Map(editModel, entity);
_dbContext.SaveChanges();
Now, instead of updating the FooEntity as well as all related BarEntitys, it gives me an InvalidOperationException with the following message:
InvalidOperationException: The instance of entity type 'BarEntity' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
How do I configure AutoMapper so that the mapper will re-use the objects in the existing collection, rather than try to replace them?
Thomas
You should understand what is going on under the hood. AutoMapper is a simple tool, which uses reflection to avoid line by line property rewriting. Nevertheless, it is creating new objects from time to time (as in your example with collections) Without collections the mapper is not creating anything - it is simply rewriting properties. If you add collections, he is handling this by creating new collections to map new collections from editModel.
This creates a situation where on DataContext, you have instantiated objects of type Bar and mapper is creating a new one which leads to conflict.
As Ivan Stoev stated, you can use Automapper.Collection to handle such situation (i never used it though, but it probably address this problem)
Related
In my ASP.NET Core we're using EF Core. When saving a record that has a list of child records we get the following error:
The instance of entity type Child cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
Step-by-step:
1. fetch record from the controller and pass it to the view (view has a )
2. update some properties via the html form
3. click save
4. catch model passed in the controller Save method. Get the original item from DB and save changes (as made via the form)
5. call update/save in the repository
Simplified code below:
// Get the record from the database
var record = _dbContext.Parents
.Include(x => p.SomeOtherObject)
.Include(x => x.ListChildren)
.FirstOrDefault(x => x.IdParent == id);
// then we do some changes to Parent and ListChildren
// we do not do any changes to SomeOtherObject!!!
// save changes
_dbContext.Update(record);
_dbContext.SaveChanges();
// definition of entities
public class Parent
{
public int IdParent { get; set; }
public string Name {get; set;}
public string Surname {get; set;}
public int IdSomeOtherObject { get; set; }
[ForeignKey("IdSomeOtherObject")]
public virtual SomeOtherObject SomeOtherObject { get; set; }
public virtual List<Child> ListChildren { get; set; }
}
public class Child
{
public int IdChild { get; set; }
public int IdParent { get; set;}
public string Name { get; set; }
}
public class SomeOtherObject
{
public int IdSomeOtherObject { get; set; }
public string PropertiesBlahBla { get; set; }
}
Now, I know that we can add .AsNoTracking() to the Get operation, but then the problem is that when saving Parent EntityFramework will perform and UPDATE SQL statement even for the SomeOtherObject (that was not changed in any way) and that is not acceptable for our data/input scenario.
Is there any other way to get pass this error?
Try removing _dbContext.Update(record);. Your entities should already be tracked, so changes should be saved.
As Update docs state:
Begins tracking the given entity and entries reachable from the given entity using the Modified state by default
So it seems that in this scenario it is not needed.
UPD
During discussions in chat was discovered that child tracked collection was substituted like this:
record.ListChildren = someModel.ListChildren.Where(...).ToList()
Which resulted in addition of elements with already tracked ids. So the ListChildren should be updated with saving already tracked items like recommended here.
I've been trying to update an entity that will have a list of child entities but for some reason I keep getting the same error and I don't know what I am doing wrong. Any help will be appreciated. The error is:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
Bellow is what I am trying:
public class Parent
{
[Key]
public int Id { get; set; }
[MaxLength(25)]
public string Name { get; set; }
[MaxLength(25)]
public string FullName { get; set; }
public virtual ICollection<Child> ChildList { get; set; } = new HashSet<Child>();
}
public class Child
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(12)]
public string Number { get; set; }
[IgnoreMap]
public Parent Parent { get; set; }
public int ParentId { get; set; }
}
public async Task<Parent> UpdateParent(Parent Parent)
{
Parent originalParent = await GetById(Parent.Id, c => c.ChildList);
Mapper.Map(Parent, originalParent);
await DbContext.SaveChangesAsync();
return originalParent;
}
Also I have noticed that if I don't use automapper and I just map the properties manually it works:
private static void MapParentProperties(Parent parent, Parent originalParent)
{
originalParent.Name = parent.Name;
originalParent.FullName = parent.FullName;
}
I haven't used Automapper with EF for a while due to past issues similar to this and lazy load trigger, but I've been digging around into it lately given it seems to have better IQueryable support. I'm not sure if it's still the case, but Automapper had a limitation that mapped child collections would be replaced by default, and with EF this could be treated as new entities being marked for insertion or existing child entities getting "bumped out" of the parent and EF flagging that their FK could not be #nulled. The solution was to tell Automapper to keep the destination collection, then handle those mappings individually. (using the child maps) There might be some additional work needed to handle inserts & deletes in the modified collection.
Something that may help:
http://bzbetty.blogspot.com/2012/06/updating-collections-using-automapper.html
Sorry to raise this one again - I see it plenty of times on here but I am still puzzled about it in my case, especially because I seem to be able to 'overcome' it, but I'm not sure I like how, and I would love to understand why.
I have a many to many relationship between Staff and Departments, with the StaffDepartment table being set up with a composite key like so:
modelBuilder.Entity<StaffDepartment>()
.HasKey(sd => new { sd.StaffId, sd.DepartmentId });
I suspect that must be something to do with the exception I sometimes get:
InvalidOperationException: The instance of entity type 'StaffDepartment' cannot be tracked because another instance with the same key value for {'StaffId', 'DepartmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I say sometimes because, strangely, if I change the department in my staff details page and update, it goes through fine, but when I leave it the same (maybe just changing the staff name for example) I receive this exception.
I tried implementing the various suggestions on this
similar but apparently different SO discussion
and all sorts of ways of tinkering with my repository update method, and strangely enough this is the only way I have got it working:
public async Task UpdateStaff(StaffDto staff)
{
var staffToUpdate = await _db.Staff
.Include(d => d.Departments)
.ThenInclude(d => d.Department)
.SingleOrDefaultAsync(s => s.Id == staff.Id);
// Without either these two line I get the exception
_db.StaffDepartment.RemoveRange(staffToUpdate.Departments);
await _db.SaveChangesAsync();
_mapper.Map(staff, staffToUpdate);
await _db.SaveChangesAsync();
}
So it seems strange that I should have to call SaveChangesAsync() twice in same method. What is actually happening there? And how would I achieve the suggestion in the error When attaching existing entities, ensure that only one entity instance with a given key value is attached without doing so.
Entity classes look like so:
public class Staff
{
public int Id { get; set; }
...
public ICollection<StaffDepartment> Departments { get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
...
public ICollection<StaffDepartment> Staff { get; set; }
}
public class StaffDepartment
{
public int StaffId { get; set; }
public Staff Staff { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
}
And the staffDto that comes back from the domain etc. is simply:
public class StaffDto
{
public int Id { get; set; }
...
public List<DepartmentDto> Departments { get; set; }
}
The repositories are all registered as transient i.e.
services.AddTransient<ICPDRepository, CPDRepository>();
First off, I'm new to the Entity Framework and am migrating an existing project from a database framework that I wrote myself so I have a fair amount of flexibility in the solution I choose.
From what I've researched so far everything appears to be set up correctly. However, when my database is constructed, the table for a helper class I wrote has no columns in it (outside of its primary key). The most simplified version of the classes are included below with their relationships defined in the fluent API.
Classes
public class Concept
{
public long ID { get; set; }
[Index(IsUnique = true), MaxLength(255)]
public string Name { get; set; }
}
public class Tag
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
public class Helper
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual List<Tag> Instances { get; set; }
// Helper functionality
}
public class Content
{
public long ID { get; set; }
public virtual Helper Helper { get; set; }
public Content() { Helper = new Helper() { Subject = this }; }
}
Context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Concept);
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Subject);
modelBuilder.Entity<Helper>()
.HasRequired(t => t.Subject)
.WithRequiredDependent(c => c.Helper);
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances);
modelBuilder.Entity<Content>()
.HasRequired(c => c.Helper)
.WithRequiredPrincipal();
base.OnModelCreating(modelBuilder);
}
Program.cs
static void Main(string[] args)
{
Content content = null;
using (var context = new Context())
{
content = context.Content.Find(1);
if (content == null)
{
content = new Content();
context.Content.Add(content);
context.Helper.Add(content.Helper);
context.SaveChanges();
}
}
}
It's also worth mentioning that when the data is saved, the Helper is assigned an ID but on loading the parent class (Content) the second time around, the Helper is not lazy loaded as I would expect from the 'virtual' keyword. I suspect that this is caused by the same issue causing the absence of data in the table.
I have tried both the data annotation and fluent API approaches that EF provides but it seems that there is something fundamental that I am misunderstanding. I would like to retain this helper class as it helps organize the code far better.
As I have spent a fair amount of time researching these relationships / APIs, and scouring Google / SO without found anything to solve this issue in particular any help would be greatly appreciated!
Updated: Solution
Thanks to a question in the comments, I realized that I was expecting to see the keys of a many-to-many relationship in the tables for the entity types themselves (i.e. in the Helpers table). However, in a many-to-many relationship, the keys will always be placed in a separate table (concatenation of type names) which was not being previously created.
By adding '.WithMany();' to the Helper section of the OnModelCreating function as below
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances)
.WithMany();
the many-to-many relationship became properly defined and the HelperTags table generated as expected. This is due to the fact that the many-to-many relationship is one way (Helpers always refer to Tags, Tags never refer to Helpers). This is also why the 'WithMany' does not have any arguments (since no Helper properties exist in the Tag class). Fixing this simple oversight solved the problem!
You are probably working harder than you need to in the on ModelCreate. You should probably redesign your classes use Identifiers, like this:
public class Tag
{
public long Id { get; set; }
public long SubjectId { get; set; }
public long ConceptId { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
You need to keep the ID names the EXACT same as the object names + Id and EF will magically link everything up. If you don't want them required then make the id nullable (C# 6 == long? SubjectId).
Also, I have changed the ID -> Id; I have no idea if this matters. At one point I remember having to do that to get things working (it was YEARS ago) and I have been doing it that way ever since.
Consider reading:
Entity Framework Code First Conventions
relationship Convention
In addition to navigation properties, we recommend that you include
foreign key properties on the types that represent dependent objects.
Any property with the same data type as the principal primary key
property and with a name that follows one of the following formats
represents a foreign key for the relationship:
<navigation property name><principal primary key property name>
<principal class name><primary key property name>
<principal primary key property name>
If multiple matches are found then precedence is given in the order
listed above.
Foreign key detection is not case sensitive.
Sample Code from MSDN:
In the following example the navigation properties and a foreign key are used to define the relationship between the Department and Course classes.
public class Department
{
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
// Primary key
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
}
I have 3 tables where Property has a foreign key to Dependent and Dependent has a foreign key to Main, causing a One-to-Many relationship for each of the tables. However I am only interested in the most recent record in Dependent and its Property records, thus i created a view v_Dependent which returns the most recent Dependant record grouped by MainId. This will enable a One-to-One relationship between Main and Dependant which is what I'm after, works with the code below.
I am eager loading all when loading Main objects, however after i switched to the view I may no longer eager load the records in the Properties collection of Dependent. The reason for this is that to map the view into a One-to-One relationship I had to add MainId to the composite key for Dependent. Now the foreign key from Property would have to contain the MainId as well to be able to load the collection, however I do not have MainId in the database table, nor do I want to.
My question is, do I have to create a view for Property as well to include the MainId and add this to the entity composite foreign key, or is there anything else I can do to map this using fluent API? Another option I'm currently using is explicitly loading the Property collection in my repository, however I was hoping fluent API could handle this for me. The commented out line is the config which worked for the whole graph while I treated the Dependent as a collection on Main. I am using the entities read only, so dont have to worry about storing back.
public class Main
{
public int MainId { get; set; }
public Dependent Dependent { get; set; }
}
public class Dependent
{
public int DependentId { get; set; }
public int MainId { get; set; }
public Main Main { get; set; }
public ICollection<Property> Properties { get; set; }
}
public class Property
{
public int PropertyId { get; set; }
public int DependentId { get; set; }
public Dependent Dependent { get; set; }
}
public class SomeContext : DbContext
{
public DbSet<Main> Mains { get; set; }
public DbSet<Dependent> Dependents { get; set; }
public DbSet<Property> Properties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Main>().ToTable("Main").HasKey(m => m.MainId);
//modelBuilder.Entity<Dependent>().ToTable("Dependent").HasKey(d => d.DependentId).HasRequired(d => d.Main).WithMany(m => m.Dependents).HasForeignKey(d => d.MainId);
modelBuilder.Entity<Dependent>().ToTable("v_Dependent").HasKey(d => new {d.DependentId, d.MainId}).HasRequired(d => d.Main).WithOptional(m => m.Dependent);
modelBuilder.Entity<Property>().ToTable("Property").HasKey(p => p.PropertyId).HasRequired(p => p.Dependent).WithMany(d => d.Properties).HasForeignKey(p => p.DependentId);
base.OnModelCreating(modelBuilder);
}
}
After alot of digging I found out that the underlying issue is that entity framework is unable to map a One to Zero or One relationship using a foreign key instead of primary key. See https://entityframework.codeplex.com/workitem/299
As a result I had to choose between changing the primary key of Dependent (which is currently a view and thus I could create an indexed view with all the limitations of such, but I have a subquery I cannot get rid of) or load the properties seperately. I chose the latter and changed the applcation to no longer have a navigation property and loading the properties when needed.