I have an Entity Framework model that has 3 tables (each with indentity primary keys). The root table has a 1 to many relationship to a child table, that child table has a 1 to many relationship with it's child table. This model is reflected correctly in the model that was generated from the database.
In code, we do an Insert (Add) into the parent table, we then do an insert of that children's tables, and then finally we do inserts on the children's children. The code looks similar to the example below:
foreach(var parentItemDTO in someDTOCollection) {
foreach(var someChildDTOItem in someChildDTOCollection) {
// Do some mapping here to the childEntity from DTO
// The foreign key relationship isn't set during mapping.
childTable.Insert(childEntity); // Underlying code is _dbSet.Add(entity)
foreach(var someChildChildDTOItem in someDTOChildChildCollection) {
// Do some mapping here to childChildEntity from DTO
// The foreign key relationship isn't set during mapping.
childChildTable.Insert(childChildEntity); // Underlying code is _dbSet.Add(entity)
}
}
// Do mapping here of the parentEntity from DTO
parentTable.Insert(someEntity); // Underlying code is _dbSet.Add(entity)
}
The inserts into the database seem to be working. However, what I would like to understand is under the hood how does EF maintain the relationship of these objects without me explicitly defining the foreign key relationship during the mapping? Is these inserts scope safe? Will this cause orphans or children being inserted into the wrong parent (right now we don't see this happening but does it have the potential)?
Thanks!
EDITED (CORRECTION):
The code has been updated to reflect that the parent insert is happening AFTER all the children inserts.
For the entities to be tracked properly by EF you need to have properties that represent relationships between entities. You parent entity should have a property referencing children and children in turn should have properties referencing their children. For example:
class ParentEntity {
public int Id { get; set; }
public ICollection<ChildEntity> Children { get; set; }
}
class ChildEntity {
public int Id { get; set; }
}
As long as you add child entities to parent's Children collection EF can keep track of relationships:
var parent = new ParentEntity();
parent.Children.Add(new ChildEntity());
parent.Children.Add(new ChildEntity());
EF knows that object references in parent.Children collection represent new entities (entities not attached to the context) and will handle them accordingly. The actual inserts to the database don't happen until you call SaveChanges(). When you add an object to DbSet EF just begins to keep track of it in memory. Only when you call SaveChanges() entities will be written to the database. At this point EF will figure out that parent entity needs to be saved first. It will then use parent entity's PK as FK in your child entities. Now you can add parent to context and this will also add children:
context.Set<ParentEntity>().Add(parent);
context.SaveChanges(); // adds parent and two children.
Related
In Entity Framework, three entities have 1 to many relationships as grandparent, children and grandchildren. How do you obtain an object list of all grandchildren given the grandparent's primary key?
Thank you,
Newby to EF
Well if you have two one-to-many relationships between those three entities, I guess you could do something like this:
int grandparentId=1;
using(var ctx=new YourContext())
{
var grandparent=ctx.GrandParents.FirstOrDefault(gp=>gp.Id==grandparentId);
if(grandparent!=null)
{
// a list with all the grandchildren
var grandchildren=grandparent.Children.SelectMany(c=>c.GrandChildren).ToList();
}
}
And, if you are not using lazy loading then you need to use the Include extension method:
int grandparentId=1;
using(var ctx=new YourContext())
{
var grandparent=ctx.GrandParents.Include(gp=>gp.Children.Select(c=>c.GrandChildren)).FirstOrDefault(gp=>gp.Id==grandparentId);
if(grandparent!=null)
{
// a list with all the grandchildren
var grandchildren=grandparent.Children.SelectMany(c=>c.GrandChildren).ToList();
}
}
But, as #ErikPhilips said, you need to give more information about your model. Without that information it's difficult to give a concrete answer to your real problem.
Maybe this is a question with an easy answer ... but I don't get it running. At persist() I get the exception that the referential key in the child table is null (which of course is not allowed by the database). I have a recipe and some steps for preparation.
I'm using EclipseLink 2.4.1
Recipe.java (rcpid is autoset by JPA)
#Entity
public class Recipe {
#Id
long rcpid;
List<Recipestep> recipesteps = new ArrayList<>();
#OneToMany(
cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="recipe",
targetEntity=Recipestep.class )
// This does NOT work. Following line tries to access a join-table !!!
// #JoinColumn(name="rcpid", referencedColumnName="rcpid")
public List<Recipestep> getRecipesteps() { return recipesteps; }
// some more attributes, getters and setters
}
Recipestep.java (rpsid is autoset by JPA)
#Entity
public class Recipestep {
#Id
long rpsid;
Recipe recipe;
#ManyToOne( targetEntity=Recipe.class )
#JoinColumn( name="rcpid" )
public Recipe getRecipe() { return recipe; }
// some more attributes, getters and setters
}
The code above is a valid workaround. However to have clean (and supportable) code, the relationship should be only one-way with a collection in the parent which references all its children.
You have mapped this as a unidirectional one to many, but have two mappings for the recipestep rcpid database column. Try changing the long rcpid to
#ManyTOne
Recipe rcp;
And then remove the joincolumn definition from the oneToMany and make it bidirectional by marking it as mappedby the rcp manyToOne relation. An example is posted here http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Relationship_Mappings/Collection_Mappings/OneToMany
Eclipselink will always insert nulls on unidirectional oneToMany relations using a joincolumn when first inserting the target entity, and then update it later when it processes the Recipe entity. Your rcpid mapping in Recipestep is also likely null, which means you have two write able mappings for the same field which is bad especially when they conflict like this.
You are experiencing the default JPA behaviour. Adding an entity to the recipesteps list is not sufficient to create a bidirectional relation.
To solve the issue you need to set the rcpid explicitly on every element in the list.
EDIT: I think the issue is that JPA does not know where to store the id of the Recipe in the Recipestep table. It assumes a name ("recipebo_rcpid"), but your table seems to lack it.
Try adding the column "recipe_id" to the Recipestep table and a mappedBy attribute to the #OneToMany annotation:
#OneToMany(
cascade=CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "recipe" )
You probably do not need the targetEntity attribute in the annotation- the List is typed already.
Suppose the following tables
ParentEntities
ParentID
ChildEntities
ChildID
ParentID
These tables do not have a FK defined in the schema.
In EF designer, after generating from DB, I add an association:
- Parent Multiplicity: 1
- Child Multiplicity: 0 or 1
When I build, I get the error: "Error 3027: No mapping specified for the following EntitySet/AssociationSet - ParentChild"
But if I try to configure table mapping for the association like this..
Maps to ChildEntities
Parent
ParentID <-> ParentID (parent entity prop <-> child table column)
Child
ChildID <-> ChildID (child entity prop <-> child table column)
.. I get this: Error 3007: Problem in mapping fragments starting at lines xxx, xxx: Column(s) [ParentID] are being mapped in both fragments to different conceptual side properties.
Why this is an error doesn't make sense. Limitation of the current implementation?
[EDIT 1]
I'm able to make this work by creating a 1-N association. That's not ideal, but it works just the same, just have to add a read-only child property in a partial:
public partial class Parent
{
public Child Child { get { return Childs.Any() ? null : Childs.First(); } }
}
This seems like the best solution for me. I had to add a FK to the database to get EF to generate the association and navigation property, but once it was added I was able to remove the FK, and further updates to the model from the DB did not remove the association or Navigation properties.
[EDIT 2]
As I was investigating how to work around not caring about the association being modeled in EF, I ran into another issue. Instead of the read-only Child property I made it normal ..
public partial class Parent
{
public Child Child { get; set; }
}
.. but now I need a way to materialize that from the query:
var query = from parents in context.Parents
// pointless join given select
join child in context.Childs
on parents.ParentID equals child.ParentID
select parents;
I can select an anonymous type ..
// step 1
var query = from parents in context.Parents
join child in context.Childs
on parents.ParentID equals child.ParentID
select new { Parent = parents, Child = child };
.. but then I've got to consume more cycles getting that into my entity:
// step 2
var results = query.Select(x => {
var parent = x.Parent;
parent.Child = x.Child;
return parent; });
Is there a better/streamlined way to do this from the query select so the EF materializer can do it from the get-go? If not, then I'll resort to Edit 1 methodology ..
Ef Code first requires 1->0..1 relationships for the Child to have the same primary key.
Maybe this a similar restriction In the modeler in this circumstance.
ParentId (Key) required in Both tables.
I have never tried adding such relationships in designer afterwords in DB first.
EDIT: to match your EDIT2:
I would stay on the direction . Use Navigation properties to get from Join back to original class A and B.
query = context.Set<JoinTable>.Where(Jt=>Jt.NavA.Id == ClassAId
&& Jt.navB.Id == ClassBId)
use a select if your need entries returned from either ClassA or ClassB.
I have the following class
public class ObjectA{
private List<ObjectB> list;
}
ObjectA and ObjectB are in N:N relation.
I want to delete only the relation and I use
while (objectA.list.Any())
objectA.list.Remove(objectA.list.First());
List is of the relation table -
List<ObjectAobjectB>
And I get
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.
EDIT: updating model definition
There are three tables in my model :
* ClassA - SchemaA,
* ClassAClassB - SchemaA,
* ClassB - SchemaB,
In my contex (and edmx ) I have only Schema A ( ClassA and ClassAClassB)
There for it is 1:N to the relation Table.
Here is the code generated from the edmx.
public partial class ClassA:DomainEntity
{
....
public virtual ICollection<ClassB> ClassAClassB { get; set; }
}
What am I doing wrong?
Thanks.
If you have one-to-many relation with non-nullable FK you must also delete ObjectB because removing it from navigation property will only remove the relation (makes FK null) but does not remove the ObjectB itself. Try this:
while (objectA.list.Any()) {
var b = b;
objectA.list.Remove(b);
entities.DeleteObject(b);
}
I have run into an interesting problem with Entity Framework and based on the code I had to use to tackle it I suspect my solution is less than ideal. I have a 1-to-Many relationship between Table A and Table B where entities in TableB have a reference to TableA. I have a scenario where I want to simultaneously delete all children of a row in TableA and I thought this could be achieve by simply clearing the collection:
Entity.Children.Clear()
Unfortunately, when I attempted to save changes this produced as a Foreign Key violation.
A relationship is being added or
deleted from an AssociationSet
'FK_EntityB_EntityA'. With cardinality
constraints, a corresponding 'EntityB'
must also be added or deleted.
The solution I came up with was to manually delete object via the entity context's DeleteObject(), but I just know this logic I am using has got to be wrong.
while (collection.Any())
Entities.DeleteObject(collection.First());
For one, the fact that I had to use a Where() loop seems far less than ideal, but I suppose that's purely a semantic assessment on my part. In any case, is there something wrong with how I am doing this, or is there perhaps a better way to clear a child entity collection of an entity such that Entity Framework properly calls a data store delete on all of the removed objects?
Clear() removes the reference to the entity, not the entity itself.
If you intend this to be always the same operation, you could handle AssociationChanged:
Entity.Children.AssociationChanged +=
new CollectionChangeEventHandler(EntityChildrenChanged);
Entity.Children.Clear();
private void EntityChildrenChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
You can build this in to your entity using a partial class.
You can create Identifying relationship between parent and child entities and EF will delete child entity when you delete it from parent's collection.
public class Parent
{
public int ParentId {get;set;}
public ICollection<Child> Children {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public int ParentId {get;set;}
}
Mapping configuration:
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.ParentId });
modelBuilder.Entity<Parent>().HasMany(x => x.Children).WithRequired().HasForeignKey(x => x.ParentId);
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>().HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// you can delete all children if you want
// parent.Children.Clear();
_context.SaveChanges();
Done!