What's the proper way to delete entities in Entity Framework? - c#

I'm having a hard time deleting an object tree. My model doesn't use any kind of built-in cascade deletion mechanism, so I have to perform the explicit deletion of each of the related entities.
The entity I want to delete has 3 levels of indirection (navigation properties)
class Parent
{
public ICollection<Child> Children { get; set; }
}
class Child
{
public ICollection<Grandchild> Grandchildren { get; set; }
}
public class Grandchild
{
}
my DbContext is
public class Context
{
DbSet<Parent> Root {get; set;}
DbSet<Grandchild> Grandchildren {get; set;}
}
Please, notice that the context doesn't expose a DbSet for the class Children.
So, what's the correct way to delete everything under a Parent?

First you need to make sure, Entity Framework has a Foreign Key.
Then you should be able to cascade delete:
Cascade delete automatically deletes dependent records or sets null to
ForeignKey columns when the parent record is deleted in the database.
Cascade delete is enabled by default in Entity Framework for all types
of relationships such as one-to-one, one-to-many and many-to-many.
So the following code should remove every children
var parent = _dbContext.Single(predicate)
_dbContext.Remove(parent);
_dbContext.SaveChanges();

I guess it will be enough to remove DbSet<> declarations you dont need and then migrate the database.
PowerShell:
dotnet ef migrations add [name]
dotnet ef database update
Edit: above applies if you used code-first approach.

Related

Entity Framework Core: Update relation with Id only without extra call

I'm trying to figure out how to deal with 'Single navigation property case' described in this doc:
Let's say we have 2 models.
class School
{
public ICollection<Child> Childrens {get; set;}
...
}
and
class Child
{
public int Id {get; set;}
...
}
So it's many-to-one relationship created by convention, without explicit foreign key in a Child.
So the question is if we have Child instance and know School.Id is there a way to update this relation without extra call to database to obtain School instance.
So the question is if we have Child instance and know School.Id is there a way to update this relation without extra call to database to obtain School instance.
Yes, it's possible. You can create a fake stub School entity instance with Id only, Attach it to the DbContext (this way telling the EF that it is existing), Attach the Child instance for the same reason, and then add the Child to the parent collection and call SaveChanges:
Child child = ...;
var schoolId = ...;
var school = new School { Id = schoolId };
context.Attach(school);
context.Attach(child);
school.Childrens.Add(child);
context.SaveChanges();
Update: Actually there is another cleaner way, since even if the entity has no navigation or FK property, EF Core allows you to access/modify the so called Shadow Properties
Shadow properties are properties that do not exist in your entity class. The value and state of these properties is maintained purely in the Change Tracker.
as soon as you know the name. Which in your case, without configuration would be by convention "SchoolId".
So no fake School entity instance is needed, just make sure the Child is attached and then simply set the shadow property through ChangeTracker API:
context.Attach(child);
context.Entry(child).Property("SchoolId").CurrentValue = schoolId;
context.SaveChanges();
Based on the updated question
No, there isn't ANY way you could do that by using ORM and strong typing that the ORM offers you, w/o
Two-Way Navigation Property
At least a ForeignKey/Principal property(SchoolId on Child)
Having a shadow foreign key to the parent
performing a raw query (which beats the idea of having ORM for strong typing) and being DB agnostic at the same time
// Bad!! Database specific dialect, no strong typing
ctx.Database.ExecuteSqlCommandAsync("UPDATE Childs SET schoolId = {0}", schoolId);
When you choose to use an ORM you have to accept certain technical limitations of the ORM framework in question.
If you want to follow Domain Driven Design (DDD) and remove all db specific fields form your entities, it won't be easy to use your domain models as entities.
DDD and ORM don't have very good synergies, there are way better approaches for this, but require a different architectural approach (namely: CQRS+ES (Command Query Responsibility Segregation with Event Sourcing).
This works much better with DDD, since the Events from the EventSourcing are just simple (and immutable) message classes which can be stored as serialized JSON in the database and replayed to reconstruct the domain entity's state. But that's a different story and one could write whole books about this topic.
Old Answer
The above scenario is only possible in a single DB operation, if your Child objects a navigation property/"back reference" to the parent.
class School
{
public ICollection<Child> Childrens {get; set;}
...
}
and
class Child
{
public int Id {get; set;}
// this is required if you want do it in a single operation
public int SchoolId { get; set; }
// this one is optional
public School { get; set; }
...
}
Then you can do something like:
ctx.Childs.Add(new Child { Id = 7352, SchoolId = 5, ... });
Of course you first have to know the school Id and know it's valid, otherwise the operation will throw an exception if SchoolId is an invalid value, so I wouldn't recommend this approach.
If you only have the childId and not adding a whole new child you'll still have to get the child first.
// childId = 7352
var child = ctx.Childs.FirstOrDefault(c => c.Id == childId);
// or use ctx.Childs.Find(childId); if there is a chance that
// some other operation already loaded this child and it's tracked
// schoolId = 5 for example
child.SchoolId = schoolId;
ctx.SaveChanges();

Code first refuses to cascade delete an entity

I have a Parent entity with a 0-to-Many relationship to a Child entity. When I delete a parent I want it to automatically cascade delete all the attached children. Attempting to do so gives the following exception...
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.
I do not understand the message talking about setting a null reference. Because of the cascade delete the children will be removed so there is no need to set any children to have null references.
My two simple entities are defined as...
public class Parent
{
[Key]
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
With the following mapping...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(true);
}
Looking at the generated database, it does indeed mark the foreign key relationship as cascade on delete. So the database schema looks fine. The actual code that throws the error...
Parent p = context.Parents.Find(id);
context.Entry<Parent>(p).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
Any ideas?
Your error is being generated by Entity Framework, not by your database.
The problem is that you're using context.Entry<Parent>(p).State = EntityState.Deleted instead of context.Parents.Remove(p). The main difference is that calling Remove on the parent handles setting the entity state to deleted for any children with a required relationship that are loaded into the context. State = EntityState.Deleted does not.
In your case, you probably have some related Child entities loaded into the context and EF is complaining about the orphaned children. If you didn't have any children loaded, the DELETE statement would be sent to the database, and the database would handle the cascade delete normally.
Using DbSet.Remove would be preferable.
See this for more details:
Delete parent with children in one to many relationship
You code matches this well so I think you configured it correctly: https://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
I don't know why this doesn't work, cascade has always worked for me so far. I suspect some configuration. I always disable lazy loading, proxy generation and validation so that could be some this to look at.
Here are some things to try:
1) Can you delete the item manually from the database and does the cascade work then?
2) Is this a sql error or an "app" error? I suspect it's an app error an that EF validation is somehow triggering. If it's from the db the exception should and SqlException. I usually check with Sql Profiler if the command get sent too.
3) If it's an app error try disabling EF's validation and see what happens.
ctx.Configuration.ValidateOnSaveEnabled = false;
4) When you use find does that load the children (might do if it's found in the local context) or does it have lazy loading enabled. Setting the state is recursive if I remember correctly and that could possibly mess up some things. You can check in the changetracker how many entities that are marked for deletion.

Undo / Redo with change tracking in Entity Framework

I'm trying to implement an Undo / Redo feature based on entity framework with POCO entities. Therefor after each change that I want to track I call the ChangeTracker for any modification to entities an I call the ObjectStateManger for changes to relationships (i.e. navigation properties). I store all the changes with the current and previous values to be able to go back and forth in the history.
My problem now is that for entities with a navigation property and a corresponding foreign key these two values are not properly synced if the referenced entity was newly added to the DbContext.
To clarify: Say I have these classes:
public class EntityFilter
{
[Key]
public Guid ID { get; set; }
public virtual ICollection<EntityConnection> IsSource { get; set; }
public virtual ICollection<EntityConnection> IsSink { get; set; }
//other stuff
}
public class EntityConnection
{
[Key]
public Guid SinkFilterID { get; set; }
[ForeignKey("SinkFilterID")]
public virtual EntityFilter Sink { get; set; }
public Guid SourceFilterID { get; set; }
[ForeignKey("SourceFilterID")]
public virtual EntityFilter Source { get; set; }
//more stuff
}
EntityConnection is basically a many-to-many relationship between filters, but it actually contains more fields which is why I cannot get rid of it. Also I want to be as general as possible and not depend on our actual data model.
The problem arises if I add a new filter and then connect it to an existing filter (could also be a new one). Undoing the connection is still ok, but when I try to redo my program will crash. I can see in the restored connection that the foreign key SinkFilterID has the correct value but Sink is null (the same might happen for source, depending on the direction of the connection). Manually calling DetectChanges makes no difference. Adding a connection between two existing filters (i.e. they are already stored in the db before) is no problem.
The detected changes for a new connection of this type only contain entity changes from the ChangeTracker and no relationship changes from the ObjectStateManger. I guess this is because the relationship is already handled by the foreign key, which is included in the properties from PreviousValues.
I've read that entities in the EntityState.Added state get temporary keys and that change tracking for them is not fully supported. Can I get this to work somehow?
I've tried to check with the MetadataWorkspace if my updated entities have a foreign key and a corresponding navigation property and in that case update it manually via reflection, but I'm not sure what data I actually have to check.
Is there a way to keep foreign keys and navigation properties to added entities in sync? Or do you have any suggestions what I might try?
Thank you very much.
Here is what I ended up with:
I keep a separate list of all the added entities. Then when I have to restore a navigation property that is backed by a foreign key I search that list and manually set the navigation property. The hardest part was to figure out how to check in the data model if this fixup was at all needed and to find the name of the corresponding property.
The overall system still has some flaws for maximum generality but it works quite well for what we need.

Removing associated entities from an EF entity

I have an enitity with many other tables connected to it. What i would like to do is, after i've created an instance of a certain entity, remove all properties that connect it to other entities/tables. Is this possible? Thanks
I believe you just assign null to single navigation properties and call Clear on children naviagation properties:
Employee
{
SomeOtherEntity SomeOtherEntityNavigation { get; set;}
ICollection<Blah> Blahs {get; set;}
}
//somewhere
anEmployee.SomeOtherEntityNavigation = null;
anEmployee.Blahs.Clear();
Then save your DBContext.
Also note, that when you initially create an instance of an entity, it will not have any relationships with other entities unless you have some magic going on somewhere in your data layer or DB that is automatically associating new entities.

edmx mapping questions

I have a Branch table that contains:
company_id, is_deleted, branch_id, branch_name, branch_code
company_id - used in order to determine which company owns the branch.
is_deleted - rows where is_deleted=true are logically deleted and I don't want to return them in my queries.
I have to map thos fields to class Branch. Class Branch have the following members:
BranchId, BranchName, BranchCode
Should I add IsDeleted member in order to map the is_deleted field? Can I filter rows with is_deleted=true if I will not map this field?
Should I add CompanyId member in order to map the company_id field? I have many tables with company_id field since it decide whice company own the row. Can I prevent adding CompanyId member when mapping those tables? When inserting, I need to supply CompanyId - I really prefer to supply it externaly and not from the Branch object.
So now you have a concrete example so we can continue in discussion from your previous question where I described some basic information about mapping to existing objects.
Should I add IsDeleted member in order
to map the is_deleted field? Can I
filter rows with is_deleted=true if I
will not map this field?
It is possible. It's called conditional mapping where your is_delete column will be used as a filter in the mapping. It has pros and cons:
Pros:
The filter is applied every time you query the entity set including a lazy loading and an eager loading. You will never get an entity with is_deleted = 1.
Cons:
You can't map is_deleted as a property in the entity. This is one global disadvantage for all columns used to support conditional mapping, table per hierarchy inheritance and independent associations - they can't be exposed as properties. So how would you soft delete your entity if you don't have the column exposed and you can't set it in the application? The only solution for this is stored procedure mapped to delete operation for your entity - btw. it is probably the best solution if you want to do soft / logical deletes because otherwise accidental call of DeleteObject on the context or a set will do hard delete in the database.
You can't map multiple conditional entities to the same table. It means you can't have conditionally mapped both undeleted and deleted entity. This can be handled by table per hierarchy inheritance.
Btw. as I know this is not available in DbContext API (EF 4.1).
Should I add CompanyId member in order
to map the company_id field? I have
many tables with company_id field
since it decide which company own the
row. Can I prevent adding CompanyId
member when mapping those tables? When
inserting, I need to supply CompanyId
- I really prefer to supply it externaly and not from the Branch
object.
Do you have a relation between the company table and the branch table in your database? In such case your Branch entity must use either independent or foreign key association with the Company entity. Association by default creates navigation property on both related entities so your Company entity will have collection of related Branches and your Branch will have a reference to the Company it belongs to. Navigation properties are the main way how to create relations in the object world. So if you want the Branch to belong to any Company you will either assign the Company to the property in the Branch or add the Branch to the collection of branches in the Company. That is the theory - it is little bit more complex with EF when using detached objects.
To avoid some problems EFv4 introduced foreign key association where dependent entity doesn't have only navigation property but also foreign key property (your country_id). You can create relation simply by assigning this property with the id of related Country.
I have already answered separate question describing differences between Independent and Foreign key associations.
Conclusion: You must use either navigation property or foreign key property to create relation between object - both these artifacts are mapped in the entity.
Now example which will also show some details you asked me yesterday. This example shows following features:
Conditional mapping (When is_deleted = 0 in mapping details)
Independent association (I have also already described how to change Independent association to Foreign key association). If you are creating the model from existing database you can check Include foreign key columns in the model in Update wizard and it will use foreign key associations instead of independent associations in the whole model.
Navigation properties on both sides of the relation
Renaming properties in conceptual model (check mapping details where nice names are mapped to database names)
Changing accessibility of the Id property setter. I already answered similar question where this was required with POCO T4 template but same must be done for custom business objects.
Support for lazy loading - check virtual keyword used in business object's code for navigation properties.
Support for tracking proxies - check virtual keyword used in business object's code for scalar properties.
Related mapped business objects will look like:
public class Branch
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual Company Company { get; set; }
}
public class Company
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
}
And context using these custom business objects can look like:
public class Context : ObjectContext
{
public Context()
:base ("name=ModelContainer")
{
Companies = CreateObjectSet<Company>();
Branches = CreateObjectSet<Branch>();
ContextOptions.LazyLoadingEnabled = true;
ContextOptions.ProxyCreationEnabled = true;
}
public ObjectSet<Company> Companies { get; private set; }
public ObjectSet<Branch> Branches { get; private set; }
}
No, you're going to need the field visible if you want to do something like filter on it, unless you use Stored Procedures.
I don't really understand this one. Why would you NOT want company_id visible if you need to use it when inserting? It's not going to hurt anything if it's there. :)

Categories