Moving objects between lists AutoMapper and EF6 - c#

I have started using this extension, and just want to say its excellent, thank you!
Now i have an issue, where an object can be moved from 1 collection, into another collection, and when i do this, i get an exception
InvalidOperationException: Multiplicity constraint violated
I am guessing this is because the object isnt being found in the original collection, and this extension is adding the object to the new collection, even though i want it too be moved, then upon saving, EF throws the exception, because i have 2 objects with the same key against my context.
But how can i get this to work?
So if i have the following object structure
MyRoot
| Collection
| MyChild
| Collection
| MyObject (1)
| MyChild
| Collection
| MyObject (2)
How can i move MyObject (1) into the same collection as MyObject (2)??
These are all basic objects, and here is some simple code
public class MyRoot
{
public int Id { get; set; }
public ICollection<MyChild> MyChildren { get; set; }
}
public class MyChild
{
public int Id { get; set; }
public int RootId { get; set; }
public MyRoot Root { get; set; }
public ICollection<MyObject> MyObjects { get; set; }
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
Each of these objects have a DTO, for the sake of this example, lets just say the objects are exactly the same, with extension DTO on the end (this is not the case in real application)
In my application, i then have an automapper profile, like so
internal class MyProfile: Profile
{
public MyProfile()
{
this.CreateMap<MyRoot, MyRootDTO>()
.ReverseMap();
this.CreateMap<MyChild, MyChildDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
this.CreateMap<MyObject, MyObjectDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
}
}
On my web api controller method, i have this, which is very simple
public async Task<IActionResult> UpdateAsync([FromBody] MyRootDTO model)
{
// get the object and all children, using EF6
var entity = await _service.GetAsync(model.Id);
// map
_mapper.Map(model, entity);
// pass object now updated with DTO changes to save
await _service.UpdateAsync(entity);
// return
return new OkObjectResult(_mapper.Map<MyRootDTO>(entity));
}
If someone could please help, that would be great!

I don't think your problem has anything to do with AutoMapper here, it's an Entity Framework problem. If you remove something from a child collection in EF, it doesn't automatically get deleted unless you either call a .Delete on it, or the key for the object is a composite key including the parent.
I would suggest making a composite key, such as the following:
public class MyObject
{
[Column(Order = 1)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[Column(Order = 0)]
[Key]
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
The [DatabaseGenerated] option keeps the Id column as an Identity - EF's default with a composite key is for no automatic identity.
You can do the same thing on your MyChild entity.

To get this to work, i didnt change EF keys, but implemented a method in my AutoMapper profile. I looped through the object to see if the child was in a different list, and if so, moved the object into that new list. This way automapper will be able to match the object based on ID still.
I added the below code into the .BeforeMap method
Not that my base level object is called Root in this example, so parameter s is type RootModel (from my web api) and parameter d is type Root (from EF). Both RootModel and Root have a collection called Sections
.BeforeMap((s, d) =>
{
// we are going to check if any child has been moved from 1 parent to another, and
// if so, move the child before the mapping takes place, this way AutoMapper.Collections will not
// mark the object as orphaned in the first place!
foreach (var srcParent in s.Sections)
{
// only loop through old measures, so ID will not be zero
foreach (var srcChild in srcParent.Children.Where(e => e.Id != 0))
{
// check if the srcChild is in the same dest parent?
var destChild = d.Sections.SelectMany(e => e.Children).Where(e => e.Id == srcChild.Id).FirstOrDefault();
// make sure destination measure exists
if (destChild != null)
{
// does the destination child section id match the source section id? If not, child has been moved
if (destChild.ParentId != srcParent.Id)
{
// now we need to move the child into the new parent, so lets find the destination
// parent that the child should be moved into
var oldParent = destChild.Parent;
var newParent = d.Sections.Where(e => e.Id == srcParent.Id).FirstOrDefault();
// remove child from children collection on oldSection and add to newSection
oldParent.Children.Remove(destChild);
// if newParent is NULL, it is because this is a NEW section, so we need to add this new section
// NOTE: Root is my based level object, so your will be different
if (newParent == null)
{
newParent = new Parent();
d.Sections.Add(newParent);
newParent.Root = d;
newParent.RootId = d.Id;
}
else
{
// change references on the child
destChild.Parent = newParent;
destChild.ParentId = newParent.Id;
}
newParent.Children.Add(destChild);
}
}
}
}
})

Related

Why this strange behavior with EF6?

I have the following situation:
I have a Parent entity in my EF6 database first datamodel
public partial class Parents
{
public Parents()
{
this.Childs = new HashSet<Childs>();
}
public int IdParent { get; set; }
public string Description { get; set; }
public virtual ICollection<Childs> Childs { get; set; }
}
and a Child entity
public partial class Childs
{
public int IdChild { get; set; }
public int IdParent { get; set; }
public string Description { get; set; }
public virtual Parents Parents { get; set; }
}
Then I populate the objects:
Parents parent = new Parents();
parent.Description = "Test parent";
List<Childs> childList = new List<Childs>();
childList.Add(new Childs { Description = "Test child 1" });
childList.Add(new Childs { Description = "Test child 2" });
Now I do this operation:
using (CUSTOMER_Entities context = new CUSTOMER_Entities())
{
//adding parent and childs to the context (WITHOUT ANY LINK OR FOREING KEY SET)
context.Parents.Add(parent);
context.Childs.AddRange(childList);
context.SaveChanges();
}
My question is: why after SaveChanges I have childs saved in db with correct "IdParent"?
Usually I do:
using (CUSTOMER_Entities context = new CUSTOMER_Entities())
{
context.Parents.Add(parent);
foreach (var child in childList)
{
parent.Childs.Add(child);
}
context.SaveChanges();
}
or
using (CUSTOMER_Entities context = new CUSTOMER_Entities())
{
context.Parents.Add(parent);
foreach (var child in childList)
{
child.IdParent = parent.IdParent;
context.Childs.Add(child);
}
}
What is the right way?
(I'm going to use singular names for entities and references)
The reason why EF connects the parents and children is that Parent has an Id = 0 and the children all have IdParent = 0.
Under the hood, EF always executes relationship fixup. This is a process in which EF tries to synchronize navigation properties (like Child.Parent and Parent.Children) with primitive primary key and foreign key values (Id, IdParent). After the children are added to the context, EF connects the parent and the children because the key values (PK and FK) are 0.
When EF inserts the data, it first inserts the parent. It immediately grabs its generated PK value from the database and writes it into the IdParent property of the children before saving them.
You can override this automatic setting of associations by setting them manually. Suppose you want to insert two parents with children:
context.Parents.Add(parent);
context.Parents.Add(parent2);
context.Childs.AddRange(childList);
This would cause an exception, because the children can't be assigned to two parents. But if you do ...
parent.Children = childList;
context.Parents.Add(parent);
context.Parents.Add(parent2);
... you've set the association manually and EF is happy.
There are multiple ways to do this, I'm quite new to EF myself so I'm not sure if one has an advantage over the other.
EF should handle most of the FK relations for you, even though you can specify manually if you want to.
EX1:
Parents P = New Parent();
P.Childs.Add(New Childs());
context.Parents.Add(P);
//Now child is part of this particular parent's collection, it should be able
//to determine which foreign key to use automatically when you...
context.SaveChanges();
EX2:
//Code here to pull parent
Parents P = context.Parents.Find(ParentID);
//Assign child to said parent during instantiation
Childs C = new Childs{ Description = "Test", Parents = P};
context.Childs.Add(C);
context.SaveChanges();
EX3:
//Use convention or annotations
//convention naming for foreign key
public partial class Childs
{
public int IdChild { get; set; }
//public int IdParent { get; set; }
public int ParentsID {get; set;}// <---
public string Description { get; set; }
public virtual Parents Parents { get; set; }
}
For example 3, see also: https://msdn.microsoft.com/en-us/library/jj679962(v=vs.113).aspx

How to attach a new entity to a single property and a list at the same time

When creating a new parent object, I need to attach a child to it in two places. I receive the error:
Unable to determine valid ordering list and single reference to child
My model looks like:
public class Child()
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
public int FavouriteChildId { get; set; }
public virtual Child FavouriteChild { get; set; }
public void AddChild(string name)
{
var child = new Child { Name = name };
Children.Add(child);
if (Children.Count() == 1)
{
FavouriteChild = child;
}
}
}
I use fluent mapping for the Entity Framework configuration:
public class ParentMap : EntityTypeConfiguration<Parent>
{
public ParentMap()
{
this.HasRequired(t => t.FavouriteChild)
.WithMany()
.HasForeignKey(d => d.FavouriteChildId);
}
}
public class ChildMap : EntityTypeConfiguration<Child>
{
public ChildMap()
{
this.HasRequired(t => t.Parent)
.WithMany(t => t.Children)
.HasForeignKey(d => d.ParentId );
}
}
When inserting into the database, I use:
// Snipped out a standard EF Database context, repositories, generics, etc.
var newParent = new Parent();
newParent.AddChild("Felix");
newParent.AddChild("Naomi");
var parentSet = context.Set<Parent>();
parentSet.Add(newParent);
context.SaveChanges();
SaveChanges() throws the error given above.
I imagine that there is something wrong with the way I have my mapping setup and that Entity Framework cannot work out which way around to insert the Child.
I cannot do this in two steps because I need there to be at least one Favourite Child (thus the FavouriteChildId integer is not nullable). I want to also avoid moving the concept of "Favourite" to the Child entity (by adding a boolean property) because the child should never know that it's the favourite. Favouritism is a facet of the parent.
Thanks in advance.
I feel that you need to re-design it.
We seem to be forcing Entity into the Chicken or the Egg dilemma here.
The Parent needs a Child to be inserted before
it because FavouriteChildId is required for Parent to be inserted.
The Child needs a Parent to be inserted before it because ParentId is needed
for Child to be inserted.
Which one should be inserted first, the Parent or the Child?

How to explicitly load relations for multiple models at once in Entity Framework?

I have a model similar to the following:
class Parent {
public int Id { get; set; }
public string Name { get;set; }
public ICollection<Child> Children { get; set; }
public GrandChildren SpecialGrandChild {
get {
return Children.SelectMany(c => c.Children).Where(...).Single();
}
}
}
class Child {
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public ICollection<GrandChild> Children { get; set; }
}
class GrandChild {
public int Id { get; set; }
public string Name { get;set; }
public int ParentId { get; set; }
public Child Parent { get; set; }
}
I also have a fairly complex query involving all three tables. From that query I want to extract all the Parent objects, and I will be displaying a property of the SpecialGrandChild for each one.
The problem is that if I do:
query.Include(p => p.Children.Select(c => c.Children));
EF will generate an ungodly sql query, and take a ton of time to build the query (on some cases over 10 seconds!). The query is cached so further calls are much faster. If I drop the Include call, I do not get such a bad first-call performance, but of course I get a worse performance as I will be doing M*N+1 queries (for each Parent, fetch the Children, and for each Child fetch the GrandChildren).
So the question is: can I explicitly load all the Children and GrandChildren for all the loaded Parents in a single call? If so, how can I do so?
I tried querying all the Childs for the currently loaded Parents as follows:
var ids = parents.Select(p => p.Id);
(from c in Childs where ids.Contains(c.ParentId) select c).Include("Children").Load();
But that call does not tell EF that all the associated Childs are loaded so it still goes to the DB when I access the association properties.
Load your data in two steps:
var dbParent = ...; // query all Parent's
var dbChild = ...; // query all Child's
var parents = dbParent.Include(p => p.Children).ToList();
dbChild.Include(p => p.Children).toList();
That should make parents have a list of all parents, and because of tracking, each parent will have each of its children.
If you have to apply a filter condition on parent, you should make it on children too.
Since I don't know enough about your parent variable and a context your parent objects are probably attached on (or maybe not) I can't give you a precise answer.
But what you're looking for should look like this where ctx is an instance of your context and Parents a DbSet Property:
IQueryable<Parent> query = ctx.Parents.Include("Children.Children");
List<Parent> myTree = query.toList();

Mapping Parent and Children relationship collections to a single table in EF6

I'm using EF6 code-first to try to map to a desired schema
The requirement is to have an organisation table. Each organisation can have zero to many parents and zero to many children
And to have a single relationship table which holds the parent and child relationships
So I'm hoping to have a POCO like this:
public class Organisation
{
public Organisation()
{
Children = new Collection<Organisation>();
Parents = new Collection<Organisation>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Organisation> Parents { get; set; }
public virtual ICollection<Organisation> Children { get; set; }
}
and mapping like this:
public class OrganisationMap : EntityTypeConfiguration<Organisation>
{
public OrganisationMap()
{
HasMany(n => n.Children)
.WithMany()
.Map(m => m.ToTable("OrganisationRelationship").MapLeftKey("ParentId").MapRightKey("ChildId"));
HasMany(n => n.Parents)
.WithMany()
.Map(m => m.ToTable("OrganisationRelationship").MapLeftKey("ChildId").MapRightKey("ParentId"));
}
}
but if I try to add a migration after setting that up I receive error message:
OrganisationOrganisation1: Name: The EntitySet
'OrganisationOrganisation1' with schema 'dbo' and table
'OrganisationRelationship' was already defined. Each EntitySet must
refer to a unique schema and table.
Is there a way to achieve this goal?
For additional info. The reason I am holding the parent relationship is that when loading the data I need to find all nodes that have no parent and treat them as a collection of roots.
If there's a better way to do that than holding a parents collection I'd be totally happy!
If I add a migration with just the children collections then all works
Thanks for the input. I'm still trying to find out if it's possible to have one or more known roots to simplify building the graph but am interested if this mapping is possible...
The data sort of looks like this:
You're defining the mappings twice - try only once, something similar to the below
public OrganisationMap()
{
HasMany(n => n.Children)
.WithMany( n => n.Parents )
.Map(m =>
m.ToTable("OrganisationRelationship")
.MapLeftKey("ParentId")
.MapRightKey("ChildId"));
}
It's kind of hard to visualize an item having multiple parents at the same level.
Since you mentioned the word root in your question I think the ideal model would have a single parent and multiple children, now the parent of the item can have a parent, this way creating a hierarchy. You can implement the idea of an optional parent for root level elements.
If you want to implement the idea of multiple parents for a single item follow #Moho answer.
If you want hierarchy try the following:
public class Organisation
{
public Organisation()
{
Children = new Collection<Organisation>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual Organisation Parent { get; set; }
public virtual ICollection<Organisation> Children { get; set; }
public IEnumerable<Organisation> Ancestors
{
get
{
var item = this;
while (item.Parent != null)
{
yield return item.Parent;
item = item.Parent;
}
}
}
}
and your map:
public class OrganisationMap : EntityTypeConfiguration&ltOrganisation>
{
public OrganisationMap()
{
HasOptional(n => n.Parent)
.WithMany(n => n.Children)
.Map(m => m.MapKey("ParentId"));
Ignore(n => n.Ancestors);
}
}
UPDATE:
you can make all the parents be loaded from the DB in a single pull by calling Include. This loads the parent for every item that is pulled.
db.Organisations.Include(x => x.Parent).Include(x => x.Children).Where(...your condition)

Entity Framework Code First - Why can't I update complex properties this way?

I'm working on a small sample project using Entity Framework 4.1 (code first). My classes look like this:
public class Context : DbContext
{
public IDbSet<Person> People { get; set; }
public IDbSet<EmployeeType> EmployeeTypes { get; set; }
}
public class Person
{
[Key]
public int Key { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
virtual public EmployeeType EmployeeType { get; set; }
}
public class EmployeeType
{
[Key]
public int Key { get; set; }
public string Text { get; set; }
virtual public ICollection<Person> People { get; set; }
}
I've saved a couple EmployeeTypes ("first", "second") to the database, and I've saved a Person who has the first type. Now I want to modify the Person. I know I can do this by loading the Person, changing properties, and then saving. But what I want to do instead, which seems to me like it ought to work, is this:
var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person {
Key = originalKey, // same key
FirstName = "NewFirst", // new first name
LastName = "NewLast", // new last name
EmployeeType = e }; // new employee type
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();
Oddly, this changes FirstName and LastName but not EmployeeType. If I get a new Context and request this Person, the EmployeeType remains set to "first" as it was before this code ran.
What do I need to do to get the navigation properties to update, and not just the scalar properties? (This is especially puzzling because for EmployeeType, the only thing that actually needs to change is the foreign key in the Person table, and that key is a scalar property.)
(By the way, I know I can do this by retrieving the Person first, then changing properties one-by-one, but as I'm using model binding in ASP.NET MVC, it seems like this way would be easier because I'll have the updated person object already in my POST method.)
You can try it different way:
var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person {
Key = originalKey, // same key
FirstName = "NewFirst", // new first name
LastName = "NewLast"}; // new last name
c.People.Attach(p); // Attach person first so that changes are tracked
c.Entry(p).Reference(e => e.EmployeeType).Load();
p.EmployeeType = e; // Now context should know about the change
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();
Other approach is exposing foreign key in your Person entity like:
public class Person
{
[Key]
public int Key { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("EmployeeType")]
public int EmployeeTypeKey { get; set; }
public virtual EmployeeType EmployeeType { get; set; }
}
This will change the type of relation between Person and EmployeeType from Independent association to Foreign key association. Instead of assigning the navigation property assign the foreign key property. This will allow you to modify relation by your current code.
Problem is that independent associations (those don't using foreign key property) are handled as separate object in state manager / change tracker. So your modification of the person didn't affect state of the existing relation neither set the new relation. I asked on MSDN how to do it with DbContext API but it is possible only if you cast DbContext to ObjectContext and use ObjectStateManager and ChangeRelationshipState.
After trying a dozen different ways to do it the EF way, I concluded that there isn't a reasonable EF Code First way to do what I'm trying to do. So I used reflection. I created this method for my class that inherits from DbContext:
public void UpdateFrom<T>(T updatedItem) where T : KeyedItem
{
var originalItem = Set<T>().Find(updatedItem.Key);
var props = updatedItem.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var value = prop.GetValue(updatedItem, null);
prop.SetValue(originalItem, value, null);
}
}
All my objects inherit from an abstract class and have a primary key property in common, so this finds the existing object with the same key as the one passed in, and then updates the existing object's from the new one. SaveChanges needs to be called afterwards.
This works for collections, although i feel like there's got to be a better way.
var properties = typeof(TEntity).GetProperties();
foreach (var property in properties)
{
if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0)
{
dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue;
foreach (var item in collection)
{
if(item.GetType().IsSubclassOf(typeof(Entity)))
{
if (item.Id == 0)
{
db.Entry(item).State = EntityState.Added;
}
else
{
db.Entry(item).State = EntityState.Modified;
}
}
}
}
}
db.Entry(e).State = EntityState.Modified;
db.SaveChanges();

Categories