Entity Framework hard cascade delete - c#

I have a SQLite DB mapped with Entity Framework.
There are 2 tables : Collections (1:n) Albums.
When I delete a collection, all related albums have to be deleted as well.
I use CollectionRepo.Delete(collection); to achieve that. It uses the following code :
public int Delete(Collection entity)
{
Context.Entry(entity).State = EntityState.Deleted;
return Context.SaveChanges();
}
The problem is: when I execute this code, Context.SaveChanges(); give me an 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.
It seems that Entity Framework wants to null on the foreign keys instead of deleting the entries. But this is absolutely not what I want because an album makes no sense without a parent (in my use case at least).
I could obviously manualy delete the albums first and then delete the empty collection but it seems to me a bit tricky. First, it seems to me that EF should be smart enough to do it on it's own to simplify the code and second, what if I have dozens of relations to collections and albums, I would end up with quite a big, hard to maintain, code base.
Collection Class
public class Collection
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual List<Album> Albums { get; set; } = new List<Album>();
}
Album class
public class Album
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
[ForeignKey("Collection")]
public long CollectionId { get; set; }
public virtual Collection Collection { get; set; }
}
DbContext child class
public class DataEntities : DbContext
{
public virtual DbSet<Collection> Collections { get; set; }
public virtual DbSet<Album> Albums { get; set; }
public DataEntities() : base("name=Connection")
{
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Album>()
.HasRequired(a => a.Collection)
.WithMany(c => c.Albums)
.HasForeignKey(a => a.CollectionId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Collection>()
.HasMany(c => c.Albums)
.WithRequired(a => a.Collection)
.WillCascadeOnDelete(true);
}
}

Applying detached object graph modifications has always been unclear in EF. This is one of the cases where it fails without a good reason.
Assuming the Collection entity passed to the Delete method has Albums collection populated (at least this is how I was able to reproduce the exception). The line
Context.Entry(entity).State = EntityState.Deleted;
does two things: attaches entity and all Album objects from the entity.Albums to the context, marks entity as Deleted, and (note!) the Album objects as Modified. This leads to incorrect behavior when you call SaveChanges, and at the end generates the exception in question.
There are two ways (workarounds) to fix this incorrect behavior.
The first one is to replace the above line with
Context.Collections.Attach(entity);
Context.Collections.Remove(entity);
The effect is similar to the described above, with the importand difference that now the related Album objects arte marked as Deleted, which allows successfully executing the SaveChanges.
The drawback is that now the SaveChanges issues a DELETE command for each Album before the command for deleting the Collection, which is inefficient and doesn't make much sense since the cascade delete would handle that perfectly inside the database.
The second option is to keep the code as is, but clear the related collection before attaching the entity:
entity.Albums = null;
Context.Entry(entity).State = EntityState.Deleted;
This allows successfully executing SaveChanges and it generates a single DELETE command only for the entity.
The drawback is that you need to write additional code and not forget any child collection which supports cascade delete, and not doing that for collections that need cascade update (i.e. with optional relation which requires updating the FK field with NULL).
The choice is yours.

Per your comments, you're mapping to a pre-existing database (EF did not generate it). CascadeOnDelete only affects the generation of the database. If the database doesn't have CascadeOnDelete configured on the table, then EF will be confused when it attempts to delete and Sqlite doesn't comply.
Also you have the mapping for the foreign key as non-nullable and required (redundant by the way) but in the database the foreign key is nullable. EF assumes that it's not valid because of what you told it.
If you fix your mapping (remove the required annotation from the CollectionID property and change its type to int? instead of just int you should fix your problem. Actually change the mapping in the DbContext class from HasRequired to HasOptional...from there it should work.
Either that or change the table definitions on your database itself.

Related

Entity Framework throwing conflict with reference constraint while deleting

I have the following entity declared
public class TransactionEvent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual List<TransactionSignInError> SignInErrors { get; set; }
}
And the context
public class TransactionAuditsDbContext : DbContext
{
public virtual DbSet<TransactionEvent> TransactionEvents { get; set; }
}
Now when I try to delete a transaction event, I want the relevant SignInError rows to be deleted as well. I realize I can do this by using cascade on delete if I had set that up in the context, too late for that now.
How can I delete successfully a transaction? I'm getting this error.
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TransactionSignInErrors_dbo.TransactionEvents_TransactionEvent_Id". The conflict occurred in database "db", table "dbo.TransactionSignInErrors", column 'TransactionEvent_Id'
I have tried clearing the SignInErrors list before deleting, that did get rid of the above error but left NULLs in the TransactionSignInErrors table.
What you want, is "Cascade on Delete": if a TransactionEvent is deleted, then you also want that all its TransactionSignInErrors are deleted.
This works on a one-to-many relation, this does not work on a many-to-many-relation.
If you have a one-to-many relation between TransactionEvents and TransactionSignInErrors, and you followed the entity framework conventions, you will have classes like
public class TransactionEvent
{
public Guid Id { get; set; }
...
// Every TransactionEvent has zero or more TransactionSignInErrors (one-to-many)
public virtual ICollection<TransactionSignInError> SignInErrors { get; set; }
}
public class TransactionSignInError
{
public Guid Id { get; set; }
...
// Every TransactionSignInError belongs to exactly oneTransactionEvent, using foreign key
public Guid TransactionEventId {get; set;}
public virtual TransactionEvent TransactionEvent { get; set; }
}
public class TransactionAuditsDbContext : DbContext
{
public DbSet<TransactionEvent> TransactionEvents { get; set; }
public DbSet<TransactionSignInError> TransactionSignInErrors {get; set;}
}
This is all that entity framework needs to know to detect the tables, the columns in the tables and the one-to-many relation between these two tables.
In entity framework the non virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
The foreign key TransactionEventId is a real column, hence it is non-virtual. TransactionEvent is not a real column, it only refers to the relation, hence it is declared virtual.
If you stick to the conventions, there is no need for attributes, nor fluent API. Only if you want non-default identifiers for tables, columns, column types or non-default behaviour for table relations, you might need attributes or fluent API.
Default behaviour is cascade on delete: if you delete a TransactionEvent, all its TransactioinSigninErrors are also deleted.
I'm not sure whether your problems arise because you have a GUID as primary key, instead of an int. If you want, you can inform entity framework about your one-to-many relation and cascade on delete in OnModelCreating:
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
// Every TransactionEvent has zero or more TransactionSignInErrors
// Every TransactionSignInError belongs to exactly one TransactionEvent
// using foreign key TransactionEventId.
// Also: cascade on delete:
modelBuilder.Entity<TransactionEvent>()
.HasMany(transactionEvent => transactionEvent.TransactionSignInErrors)
.WithRequired(transactionSignInError => transactionSignInError.TransactionEvent)
.HasForeignKey(transactionSignInError => transactionSignInError.TransactionEventId)
.WillCascadeOnDelete();
So three major changes to your code:
The DbSets in the DbContext are non-virtual
Added the table TransactionSignInErrors to your DbContext
If that is not enough for CascadeOnDelete (check this first!) add fluent API.
Small change: Use ICollection instead of IList.
Rationale: if you fetch a TransactionEvent with its TransactionSignInErrors, does TransactionEvent.SignInErrors[4] have a defined meaning? Wouldn't it be better if people have no access to methods that they don't know what they really mean?
If you want to use a cascade delete you have to include the children:
var removingRow=_context.Set<TransactionEvent>()
.Include(x=> x.SignInErrors )
.Where(x => x.Id ==id)
.FirstOrDefault();
if(removingRow != null)
{
_context.Remove(removingRow);
_context.SaveChanges();
}
Your post has the tag of entity-framework. I'm not sure how things work with Entity Framework 6 or previous versions, but with Entity Framework Core you can solve your issue like -
var tEvent = dbCtx.TransactionEvents
.Include(p=> p.SignInErrors)
.FirstOrDefault(p => p.Id == id);
foreach (var error in eventx.SignInErrors)
{
dbCtx.SignInErrors.Remove(error);
}
dbCtx.TransactionEvents.Remove(tEvent);
dbCtx.SaveChanges();

EF 6 calls INSERT instead of UPDATE

This could be a duplicate question but a lot of searching for the words in the title only got me a lot of unrelated results.
I have an entity that's roughly set up like this:
public abstract class A
{
public GUID AId { get; set; }
public string SomeProperty { get; set; }
}
public class B : A
{
public string SomeOtherProperty { get; set; }
}
The context has public DbSet<B> BInstances { get; set; } for B objects. In OnModelCreating, the mapping has A set to ignored and B is mapped to a table called TableB.
The AId field is not auto-generated (not an identity field) but it's set to be primary key, both in the database and in the mapping. In the database, the field is defined as a non-null uniqueidentifier with no default.
At runtime, I'm loading an instance of B using its key (_token is just a CancellationToken):
var b = await (dbCtx.BInstances.FirstOrDefaultAsync(e => e.AId), _token));
Then, a property of b is set and I try to save it back to database:
b.SomeOtherProperty = "some new text";
await (dbCtx.SaveChangesAsync(_token));
At this point, I'm getting a Violation of PRIMARY KEY constraint error from the database, stating that the value of AId cannot be inserted because it'd be a duplicate. Of course, the ID is already in the database, I loaded the entity from there, using the ID. For some reason, EF generates an INSERT statement, not an UPDATE and I don't understand why.
When I check dbCtx.Entry(b).State, it's already set to EntityState.Modified. I'm at a loss - can someone point out what I'm doing wrong? I never had issues with updating entities before but I haven't used EF with GUID primary keys (usually I use long primary keys).
I'm using EF 6 and .NET Framework 4.7.1.
Thank you all for the suggestions - this turned out to be a mapping problem that I caused.
In my OnModelCreating() call, I called MapInheritedProperties() on a type that didn't inherit from a base class (other than object, of course) - this seems to have triggered a problem. Other entities that do share a base class worked fine with the mapping call.
I also called ToTable() directly against the entity class - this broke my table mapping for reasons I do not understand. Once I moved that call inside Map(), it started working as expected.
So I went from this:
entity.ToTable("tablename");
to this:
entity.Map(m => m.ToTable("tablename"));
to solve the problem.
Hopefully this will be useful for future readers.
try this
b.SomeOtherProperty = "some new text";
dbCtx.BInstances.AddOrUpdate(b);
await (dbCtx.SaveChangesAsync(_token));
AddorUpdate will update your b instance if it is already added.

Error while trying to cascade-delete

I'm getting the following error message while trying to delete an item from the db:
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've read many topics about this issue, but none of them seem to help (or maybe i didn't understand them very well).
my models are:
public class ARDOperation
{
[Key]
public int ARD { get; set; }
[Required]
public virtual ICollection<Act> Actions { get; set; }
public ARDOperation()
{
this.Actions = new List<Act>();
}
}
public class Act
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ARDID { get; set; }
public int ARDOperationId { get; set; }
[ForeignKey("ARDOperationId")]
public virtual ARDOperation ARDOperation { get; set; }
public string Data { get; set; }
[EnumDataType(typeof(ARDState))]
public ARDState State { get; set; }
}
I Also defined a fluent API:
public class ARDOperationDBContext : DbContext
{
public DbSet<ARDOperation> ARDOperation { get; set; }
//public DbSet<Act> Act { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Act>()
.HasRequired(t => t.ARDOperation)
.WithMany(t => t.Actions)
.HasForeignKey(d => d.ARDOperationId)
.WillCascadeOnDelete(true);
//modelBuilder.Entity<ARDOperation>()
}
The method in controller:
internal void RemoveAction(int ARDID)
{
var op = ARDOperationDB.ARDOperation.Find(ARDID);
if (op != null)
{
//will not remove the "idle" action
if (op.Actions.Count > 1)
{
Act act = op.Actions.ElementAt(1);
op.Actions.Remove(act);
//ARDOperationDB.Entry(op).State = EntityState.Modified;
ARDOperationDB.SaveChanges();
}
}
}
I've tried to define the "ARDOperationId" property as nullable (int?) using code-first approach and i'm not getting any errors this way, but the child's data still remain in the DB.
I think that i'm missing something related to the access to the Act model.
Will appreciate any help,
Yuval.
Take a look at [this answer][1] from an EF guru about the remove method.
EntityCollection.Remove(childEntity) marks the relationship between
parent and childEntity as Deleted. If the childEntity itself is
deleted from the database and what exactly happens when you call
SaveChanges depends on the kind of relationship between the two:
If the relationship is optional, i.e. the foreign key that refers from
the child to the parent in the database allows NULL values, this
foreign will be set to null and if you call SaveChanges this NULL
value for the childEntity will be written to the database (i.e. the
relationship between the two is removed). This happens with a SQL
UPDATE statement. No DELETE statement occurs.
If the relationship is required (the FK doesn't allow NULL values) and
the relationship is not identifying (which means that the foreign key
is not part of the child's (composite) primary key) you have to either
add the child to another parent or you have to explicitly delete the
child (with DeleteObject then). If you don't do any of these a
referential constraint is violated and EF will throw an exception when
you call SaveChanges - the infamous "The relationship could not be
changed because one or more of the foreign-key properties is
non-nullable" exception or similar.
If the relationship is identifying (it's necessarily required then
because any part of the primary key cannot be NULL) EF will mark the
childEntity as Deleted as well. If you call SaveChanges a SQL DELETE
statement will be sent to the database. If no other referential
constraints in the database are violated the entity will be deleted,
otherwise an exception is thrown.
[1]:
Entity Framework .Remove() vs. .DeleteObject()
So i read a bunch of articles on this subject. Chris's response here was really good and helpful for my understanding: Link
But what really helped me was the small code example here: Solution.
The "[Key, ForeignKey("Order"), Column(Order = 1)]" part really did the trick.
Many Thanks!

One-To-Many AND Recursive relation - Force value to be set

Environment
Framework 4.5
Entity Framework 6 (code First)
Model
MainEntity One to many ChildEntity relationship
ChildEntity One to many recursive ChildEntity relationship
Model in code Snippet
public class MainEntity
{
public long Id { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public long Id { get; set; }
public MainEntity ParentMainEntity { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
Note : A ChildEntity can only ONE level deep again ChildEntities as childeren.
Problem
I am able to persist this model. This seems to work fine. Only one issue. When I store a ChildEntity that has a parent ChildEntity. The MainEntity_id field is NULL, only the ParentChildEntityId is set. The reason I want to have the MainEntity_Id field always set is for performance queries.
How can I force that the MAINENTITY_ID field has always a value set in the deeper level?
First:
You are giving yourself a hard time by leaving the foreign keys out of your POCO's. Adding a MainEntityId property to your ChildEntity will enable you to set the relation of new ChildEntities to a MainEntity.
Second:
var newChild =new ChildEntity();
parentEntity.ChildEntities.Add(newChild);
parentEntity.ParentMainEntity.ChildEntities.Add(newChild);
should work depending on how you have loaded the entities and which entities are or will be attached to the dbContext.
You just need to define your relationship between MainEntity and ChildEntity to be required. You may do it in two ways:
place [Required] attribute over ParentMainEntity property in ChildEntity
use fluent api. In your DbContext class override OnModelCreating method and in it place code:
modelBuilder.Entity().HasRequired(e => e.ParentMainEntity).WithMany(e => e.ChildEntities);
I would like as well to recommend you to make all your entities properties virtual. When all will be virtual then ef instead of working with your entity classes will create its own DynamicProxy classes deriving from your classes. They will provide additional tracking functionalities, they automatically change values of navigation properties if related objects changes etc. EF seems to deal much better with them. To use that functionality for newly created objects you will need to create them with context.ChildEntities.Create() method instead of using constructor. Of course as this adds constrain on your ChildEntity objects you may encounter exception during persisting data to db in SaveChanges. If the above change is the only one, that you've applied it is very probable that there is at least one ChildEntity object that do not have MainEntity object assigned to it.

Entity Framework: Dependencies due to foreign key constraints

I have been looking around for a solution and cannot find good information. A lot of EF documentation is very out of date. Here is my problem.
If I add a child to a parent via the ParentID navigation property, everything works fine.
If I add a child to a parent via the Children list and the child is pre-existing, I get this exception:
DbUpdateException: Unable to determine
a valid ordering for dependent
operations. Dependencies may exist due
to foreign key constraints, model
requirements, or store-generated
values.
If I add a child to a parent via the Children list and the child is new (also needs to be persisted), I get this exception:
DbUpdateConcurrencyException: Store
update, insert, or delete statement
affected an unexpected number of rows
(0). Entities may have been modified
or deleted since entities were loaded.
Refresh ObjectStateManager entries.
Node.cs
public class Node
{
public long ID { get; private set; }
public long? ParentID { get; set; }
public List<Node> Children { get; set; }
}
Relevant DbContext.cs
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Node>()
.HasMany(c => c.Children)
.WithOptional()
.HasForeignKey(c => c.ParentID);
}
Have you tried making your Children property
public virtual ICollection<Node> instead of a List<Node>
I had the same issue, not sure what is the best solution but i tried to call SaveChanges. context.SaveChanges() and then Add the Child to Newly Created Entity and it worked.

Categories

Resources