Entity Framework: Dependencies due to foreign key constraints - c#

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.

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 Core - self referencing entity

I have a table that should refer to the same table. But when I try to update the database schema, it throws me this error:
When i try to update the schema, PMC throws this error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN
KEY constraint 'FK_Directory_Directory_SubOfId' on table 'Directory'
may cause cycles or multiple cascade paths. Specify ON DELETE NO
ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
I tried setting ON DELETE to CASCADE but nothing, still the same error. I do not know how to set NO ACTION because mapping does not offer this option.
What with this?
Entity:
public class Directory : BaseEntity
{
public int ID { get; set; }
public int? SubOfId { get; set; }
[ForeignKey("SubOfId")]
public Directory SubOf { get; set; }
public virtual ICollection<ImageDirectory> ImageDirectory { get; set; }
}
Model builder:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
...
builder.Entity<Directory>().HasOne(e => e.SubOf).WithOne().HasForeignKey<Directory>(a => a.SubOfId).OnDelete(DeleteBehavior.Cascade);
}
This exception isn't due to self-referencing. You get this when an entity can be deleted via multiple cascade paths. Based on the code you've provided, my best guess is that something going on with ImageDirectory and relationships in play there is actually the source of the issue.
Long and short, you need to investigate your object graph to see where removing one entity type might cause multiple cascades. Then, you'll need to shut off some of those cascades to proceed. There's not much more that can be said to help you, unfortunately, without being able to see all your entities and the relationships between them all.

Entity Framework hard cascade delete

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.

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!

NHibernate EventListeners - getting the value of a property of the entity being saved

I'm implementing a custom EventListener to save auditing information in NHibernate.
I'm currently extending DefaultSaveOrUpdateEventListener, overriding PerformSaveOrUpdate, going through the properties of each entity and saving them elsewhere.
This works with simple properties, but fails when cascade-saving a one-to-many relationship.
If I take the following entities:
[ActiveRecord]
public class Child
{
[PrimaryKey(PrimaryKeyType.GuidComb)]
public Guid Id { get; set; }
[BelongsTo]
public Parent Parent { get; set; }
}
[ActiveRecord]
public class Parent
{
[PrimaryKey(PrimaryKeyType.GuidComb)]
public Guid Id { get; set; }
[HasMany(Cascade = ManyRelationCascadeEnum.SaveUpdate)]
public IList<Child> Children { get; set; }
}
And then save a parent with a child:
ActiveRecordMediator<Parent>.Save(new Parent
{
Children = new List<Child>
{
new Child()
}
});
The child will get the correct parent assigned to it when its persisted to the database but the 'Parent' property of the child is null when my EventListener is called.
How can I get the value that will actually be persisted to the database in this case?
[EDIT] I've recently been looking at getting this to work by hooking the cascade and seeing what else was being saved at the time, but that seems horribly unreliable and I'd much prefer to get the data out of NHibernate so I know it's consistent with the database.
I'm not sure how you can accomplish this with ActiveRecord but it has to do with the mechanism in which NHibernate persists parent/child relationships.
Saving the child cascade prior to saving the parent in NHibernate is by design depending on which end of the relationship is marked as "inverse=true" and the child needs to have a "not-null=true" attribute on the element (which determines which end owns the relationship). This will make it so the Child is managing the state of the relationship.
Then you can simply save the child, and the parent will be updated with the appropriate information. This will generate one INSERT statement, instead of an INSERT AND UPDATE that you are probably seeing now. Not sure if this solves your problem, but I believe the problem you are having is around this behavior. You can read more at this link:
https://www.hibernate.org/hib_docs/nhibernate/html/example-parentchild.html
I see that you use Castle ActiveRecord. I was experimenting with it also.
There is some weirdness in it, because in the code you provided, the Child object's Parent property will only be set after your stuff is saved to the database. Until then, its value will be null. (I don't know if this behaviour is specific to ActiveRecord, or also NHibernate.)
Perhaps if you assign the Parent properties of the Child objects by hand, it will work.
var parent = new Parent();
var child = new Child()
{
Parent = parent
};
parent.Children.Add(child);
ActiveRecordMediator<Parent>.Save(child);
ActiveRecordMediator<Parent>.Save(parent);
Maybe the order in which you save the entities also has to do something with this matter.
I don't use ActiveRecord, I use NHibernate instead so I'm going to assume that they handle parent-child relationships in the same way (https://www.hibernate.org/hib_docs/nhibernate/html/example-parentchild.html)
What happens if you leave the ORM to manage the link to the parent (by setting Inverse=true in the HasMany attribute)?
[ActiveRecord]
public class Parent
{
[PrimaryKey(PrimaryKeyType.GuidComb)]
public Guid Id { get; set; }
[HasMany(Cascade = ManyRelationCascadeEnum.SaveUpdate, Inverse=true)]
public IList<Child> Children { get; set; }
}

Categories

Resources