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
Related
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);
}
}
}
}
})
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?
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();
I'm trying to create a database containing a single self-referenced table "Categories" using Code First approach. Here is the definition of the Category POCO entity:
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; private set; }
public string Name { get; set; }
public int? ParentCategoryId { get; private set; }
[ForeignKey("ParentCategoryId")]
public Category ParentCategory { get; set; }
public List<Category> SubCategories { get; set; }
public Category()
{
SubCategories = new List<Category>();
}
}
and here is the definition of the DbContext subclass for the database:
public class CategoryContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public CategoryContext()
: base("name=CategoriesEntities") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Category>().HasMany(cat => cat.SubCategories).WithOptional(cat => cat.ParentCategory).HasForeignKey(cat => cat.ParentCategoryId);
}
}
Now I'm trying to fill the table:
using (var context = new CategoryContext())
{
var child = new Category { Name = "child" };
var parent = new Category { Name = "parent" };
parent.SubCategories.Add(child);
//child.ParentCategory = parent;
context.Categories.Add(child);
//context.Categories.Add(parent);
context.SaveChanges();
}
But the only record I see in a resulting table is the "child" record. But if I change parent.SubCategories.Add(child) line to child.ParentCategory = parent line, everything will work fine and the table will contain both records. Everything will be ok also if I change context.Categories.Add(child) to context.Categories.Add(parent).
So, what am I doing wrong? Why isn't parent record added to the table along with it's child record? How can I achieve the desired behavior without making the substitutions listed above?
Any help will be appreciated.
You are getting this behavior because you are only saying it to add chield
context.Categories.Add(child);
and if you look at your child object it has no association with you parent , but you parent have association with child (one way relationship) so when you do context.Categories.Add(child); EF has no clue about parent
So right way to do is to add parent object only
context.Categories.Add(parent);
Changed code should look something like
using (var context = new CategoryContext())
{
var child = new Category { Name = "child" };
var parent = new Category { Name = "parent" };
parent.SubCategories.Add(child);
context.Categories.Add(parent);
context.SaveChanges();
}
If this had helped you don't forgot to mark it as answer :)
I have the following two classes:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
public Parent()
{
Children = new List<Child>();
}
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
And then the Fluent setting in the data context:
modelBuilder.Entity<Child>()
.HasKey(c => new { c.Id, c.ParentId })
.HasOptional(c => c.Parent)
.WithMany(p => p.Children)
.WillCascadeOnDelete(true);
I'm doing this so that I actually delete the child object when I say parent.Children.Remove(aChild);, not just set its ParentId null.
The problem is, I'm getting the error "Violation of PRIMARY KEY constraint 'PK_dbo.Child'. Cannot insert duplicate key in object 'dbo.Child'. The duplicate key value is (0, 2)." when I create a fresh parent with children, and then db.SaveChanges():
Parent p = new Parent { Name = "Quarterbacks" };
p.Children.Add(new Child { Name = "Brady" });
p.Children.Add(new Child { Name = "P. Manning" });
p.Children.Add(new Child { Name = "Kaepernick" });
p.Children.Add(new Child { Name = "Wilson" });
p.Children.Add(new Child { Name = "Rodgers" });
db.Parents.Add(p);
db.SaveChanges();
I thought integer primary keys are auto-generated on insertion. What should I do? Should I change the keys to strings and create GUID keys in C# for this to work?
I believe that for composite keys no part of the key is marked as an identity by default, even not for integer keys (which would be the case for a simple, non-composite key). You probably must add the Identity option explicitly to the Child entity's Id property:
modelBuilder.Entity<Child>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
I'm a bit worried that your second part of the Child's key is nullable. Perhaps EF does allow that, but the database not necessarily: I think, with SQL Server for example nullable key parts are forbidden. Maybe, other databases can deal with that. You're apparently aiming for an identifying relationship (the one that deletes the child from the database when it is removed from the parent) which however needs a required (not an optional) relationship between parent and child, as far as I know.