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!
Related
Following the guide lines from Domain Driven Design, I try to avoid having one aggregate referencing a different aggregate. Instead, an aggregate should reference another aggregate using the other aggregate's id, for example:
public class Addiction
{
private Addiction(){} //Needed for EF to populate non-simple types
//DrugType belongs to the aggregate,
//inflate when retrieving the Addiction from the db
//EF does not need DrugId for navigation
Drug Drug{get;set;}
//The supplier is not part of the aggregate,
//aggregates only reference eachother using Ids
int SupplierId{get;set;}
//Other properties
}
public class AddictionConfiguration : IEntityTypeConfiguration<Addiction>
{
builder.HasOne(addiction => addiction.Drug); //Works
builder.HasOne("SupplierId") //Does not work.
}
In this (not very realistic) example, Drug is part of the Addiction's aggregate. When loading this entity from the database using EF, it will also inflate the Drug property without me having to specify the DrugId as the foreign key.
However, now I need to get a list of all Addictions and their suppliers by mapping the relevant properties to a Dto. I try to achieve this by using AutoMapper's ProjectTo functionality, e.g.
_mapper.ProjectTo<AddictionDto>(_dbContext.Addictions.Where(x => x.Id > 1));
where AddictionDto is defined as
public class AddictionDto
{
DrugDto Drug {get;set;}
SupplierDto Supplier {get;set;}
//other properties
}
And
public class SupplierDto
{
public int Id {get;set;}
public string Name {get;set;}
}
Automapper correctly loads the Addiction and also the Drug, but I cannot get it to load the Supplier. I've tried all the options of the IEntityTypeConfiguration to tell EF that there is a navigation property, but I cannot get it to work. Does anyone know if is even possible to do what I described above?
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 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.
I have the following table defined through code-first:
public class Action
{
public int ActionId { get; set; }
public int? EmailMessageId { get; set; }
public virtual EmailMessage EmailMessage { get; set; }
}
public class EmailMessage
{
public int EmailMessageId { get; set; }
public string Content { get;set;
}
When an action is created with a corresponding EmailMessage, deleting the action doesn't remove the entry in EmailMessage. It seems EF only creates a cascade delete on 1-many relationships. In this case the relationship is 0 or 1 relationship which has no cascade delete set by default.
I then added the fluent configuration:
modelBuilder
.Entity<Action>()
.HasOptional(x =>x.EmailMessage)
.WithMany()
.HasForeignKey(x=>x.EmailMessageId)
.WillCascadeOnDelete(true);
Which seems to correctly set the CASCADE DELETE when viewing the schema in Management Studio. But when I remove the row from Action manually from the db, the row in EmailMessage remains.
What exactly am I doing wrong here?
I thought I might be getting somewhere when I used 'WithOptionalDependent()' in the configuration. However when I looked at the schema, it had introduced "EmailMessage_EmailMessageId", when I already had EmailMessageId in the table?
Can anyone advise what is wrong here?
The cascade delete is designed to remove the child when the parent is deleted, not the other way around. In this case, the Action is the child associated with the foreign-key of the EmailMessage parent. So deleting the Action will not affect the EmailMessage, but deleting the EmailMessage should cascade delete to the Action.
I have just realized when using "contextName.Entry(parentStudySession).State = System.Data.Entity.EntityState.Deleted;" to delete some parent with a few children..I talk for EF 6.3, EF doesnt't delete the children for us, even didn't delete the parent itself even though we have got cascading on delete constraint. Instead, it gives an error "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 ... "
Instead, when I use "contextName.StudySession.Remove(parentStudySession);" ,
EF 6.3 successfully deletes the PARENT and the CHILDREN... I have cascade on delete constraint.
T.Adakoğlu
I have an Entity. Mandate. Every mandate has a required:many relation to a Person (NavigationProperty). I use the DbContext API with (LazyLoadingEnabled, AutoDetectChangesEnabled, ValidateOnSaveEnabled, ProxyCreationEnabled)
Now I like to delete a Mandate entity. The mandate entities are loaded by another context with AsNoTracking().
message.Result.
ObserveOn(On<DataComposition>.Scheduler).
Where(r => r).
Subscribe(_ =>
{
using (var unit = UnitOfWork.Begin())
{
var mandate = this.SelectedItem.OriginalEntity;
this.mandateRepository.Attach(mandate);
// mandate.Person.ToString();
this.mandateRepository.Delete(mandate);
unit.Commit();
}
this.List.RemoveOnUi(this.SelectedItem);
});
Now during committing I get the following exception: Entities in 'CodeFirstContainer.Mandates' participate in the 'Mandate_Person' relationship. 0 related 'Mandate_Person_Target' were found. 1 'Mandate_Person_Target' is expected.
The delete works if I include the Person Property during the population/selection or if I visit the Property (lazyloading), but I DONT LIKE to materialize/hold many entities only for the deletion case and I DONT LIKE to trigger more than a single DELETE query to db!
The fact that, if you have the navigation property mandate.Person populated, the following SQL statement ...
delete [dbo].[Mandates]
where (([Id] = #0) and ([PersonId] = #1))
... is sent to the database, lets me think that the navigation property indeed must be populated with a person with the correct PersonId to delete the parent.
I have no idea why Entity Framework just doesn't send a delete statement with the primary key ...
delete [dbo].[Mandates]
where ([Id] = #0)
... as I had expected.
Edit
If the Mandate entity has a foreign key property PersonId for the Person navigation property, the expected SQL (the second above) is sent to the database. In this case the Person navigation property can be null and the value of the FK property PersonId doesn't matter.
Edit 2
If you don't want to introduce a FK property the way with the least DB-roundtrip-costs would probably be to fetch the person's Id and then create a dummy person with that key in memory:
// ...
var personId = context.Mandates
.Where(m => m.Id == mandate.Id)
.Select(m => m.Person.Id)
.Single();
mandate.Person = new Person { Id = personId };
this.mandateRepository.Attach(mandate);
this.mandateRepository.Delete(mandate);
// ...