Entity Framework throwing conflict with reference constraint while deleting - c#

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();

Related

The property is part of a key and so cannot be modified or marked as modified (ForeignKey is not a key)

I have two entities with a relationship without a key, when I want to edit the personnel code field in Personnel, this error is displayed to me.
'The property 'Personnel.PersonnelCode' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.'
The EnrolNumberPersonnelNum table also does not have a record that has a relationship with the changed personnel record in my table
personnel class
public class Personnel : BaseEntity, IBaseEntityTypeConfiguration<Personnel>
{
public Personnel()
{
EnrolNumberPersonnelNums = new HashSet<EnrolNumberPersonnelNum>();
}
[Key]
public int PersonnelId { get; set; }
public string PersonnelCode { get; set; }
public virtual ICollection<EnrolNumberPersonnelNum> EnrolNumberPersonnelNums { get; set; }
public void Map(EntityTypeBuilder<Personnel> builder)
{
builder.ToTable("Personnel", "HR");
builder.Property(e => e.PersonnelCode).HasMaxLength(50);
}
}
EnrolNumberPersonnelNum class
public class EnrolNumberPersonnelNum : BaseEntity, IBaseEntityTypeConfiguration<EnrolNumberPersonnelNum>
{
public EnrolNumberPersonnelNum()
{
}
[Key]
public int EnrolNumberPersonnelNumId { get; set; }
public string PersonnelCode { get; set; }
public virtual Personnel Personnel { get; set; }
public void Map(EntityTypeBuilder<EnrolNumberPersonnelNum> builder)
{
builder.ToTable("EnrolNumberPersonnelNum", "HR");
builder.Property(e => e.PersonnelCode)
.HasMaxLength(50);
builder.HasOne(c => c.Personnel).WithMany(c => c.EnrolNumberPersonnelNums)
.HasForeignKey(c => c.PersonnelCode).HasPrincipalKey(c => c.PersonnelCode)
.OnDelete(DeleteBehavior.ClientNoAction)
.HasConstraintName($"FK_{nameof(EnrolNumberPersonnelNum)}_{nameof(Personnel)}_{nameof(PersonnelCode)}");
}
}
Error message when I want to edit personnel code in personnel entity
and EnrolNumberPersonnelNum table is empty .
HasPrincipalKey(c => c.PersonnelCode) here
builder.HasOne(c => c.Personnel).WithMany(c => c.EnrolNumberPersonnelNums)
.HasForeignKey(c => c.PersonnelCode).HasPrincipalKey(c => c.PersonnelCode)
.OnDelete(DeleteBehavior.ClientNoAction)
.HasConstraintName($"FK_{nameof(EnrolNumberPersonnelNum)}_{nameof(Personnel)}_{nameof(PersonnelCode)}");
marks the property PersonnelCode of the Personnel entity as alternate key:
An alternate key serves as an alternate unique identifier for each entity instance in addition to the primary key; it can be used as the target of a relationship. When using a relational database this maps to the concept of a unique index/constraint on the alternate key column(s) and one or more foreign key constraints that reference the column(s).
And in EF Core all type of keys (primary and alternate) are read only, i.e. EF Core does not allow updating them (are required and can be provided only for new entities). This is basically what the error message is telling you at the beginning
The property 'Personnel.PersonnelCode' is part of a key and so cannot be modified or marked as modified.
With that being said, there is no solution so far for such type of model, if you need that property editable. The suggestion in the error message
To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.
doesn't seem viable, since obviously you cannot delete dependents (EnrolNumberPersonnelNums in you case) and then associate them with a new parent (since they are deleted). Eventually you can try loading them in memory, then call RemoveRange, modify the parent key, then SaveChanges, then update PersonnelCode of the cached children instances, call AddRange followed by SaveChanges. Looks unreliable/error prone, but worth trying.
If you are allowed to modify the database, better remove the PersonnelCode from EnrolNumberPersonnelNum and create and use regular FK int PersonnelId bound to the PK of the Personnel. This way you can remove the alternate key (and add just unique constraint if needed) as mentioned in the documentation
Tip
If you just want to enforce uniqueness on a column, define a unique index rather than an alternate key (see Indexes). In EF, alternate keys are read-only and provide additional semantics over unique indexes because they can be used as the target of a foreign key.
That will allow updating the PersonnelCode of Personel as any other property.

How to migrate an already set up one-to-many relationship in Entity Framework?

I have a C# WPF application using an SQLite database with Entity Framework. I have a Contact class which can have multiple messages, so there is a one-to-many relationship, set up the following way (simplified version):
public class Message {
public int PK { get; set; }
public int SenderKey { get; set; }
public Contact Sender { get; set; }
}
public class Contact {
public int PK { get; set; }
public ICollection<Message> Messages { get; set; }
}
Then I set the relationship using the Fluent API, the following way:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
...
modelBuilder.Entity<Message>()
.HasOptional(e => e.Sender)
.WithMany(e => e.Messages)
.HasForeignKey(e => e.SenderKey);
}
I'm going to have lots of data (and also have lots of troubles with this circular dependency, especially on editing the objects in detached mode) so it's not a good idea to have that Messages collection in the memory for every contact. To avoid this, I'd like to get rid of that list, so it would be great to implement the 'Convention 1' from the docs (to just have the Contact object in the Message class and that's all).
The problem with this solution is that my app is already published, so I can't just simply change the structure, I need a migration. My question is that how can I migrate this kind of relation set up by the Fluent API?
I tried to remove the relationship from the OnModelCreating, but I got the following exception when I started the app:
System.Data.SQLite.SQLiteException: SQL logic error no such column: Extent1.Sender_PK (what is that Extent1 table?)
Finally I found the solution. I didn't have to implement a specific migration, just modify the following things:
Remove the relationship setup from the OnModelCreating
The previous step caused that "sql logic error" posted in the question. This was because the foreign key column name wasn't specified and the Entity Framework searched for a default column, which is in fact Sender_PK. So, in order to solve this, I added an annotation in the Message class, which tells the Entity Framework what is the foreign key column name for that Contact object:
public class Message {
public int PK { get; set; }
public int SenderKey { get; set; }
[ForeignKey("SenderKey")]
public Contact Sender { get; set; }
}
Remove the message list reference (public ICollection<Message> Messages { get; set; }) from the Contact class.
So, after this three step modification I had the one-to-many relationship between the two tables and I could get rid of that list. Nothing else needed, it works perfectly with the old databases.

Unidirectional One to One relationship entity framework, cascade delete doesn't work

I want to implement a unidirectional one to one relationship; however on cascade delete doesn't work.
I have the following classes:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
//I don't want the StudentId foreign key or the property of Student class here
}
In my Context class, I'm mapping the relationship like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasRequired(s => s.Address)
.WithOptional()
.Map(m => m.MapKey("Address_Id"))
.WillCascadeOnDelete();
}
For some reason, it's not deleting the address when, student object is deleted.
Moreover, I also want to add the foreign key property (i.e. AddressId) in the Student class like this:
[ForeignKey("Address")]
[Column("Address_Id")]
public string AddressId { get; set; }
However, I get this error when I try to add a new migration:
Address_Id: Name: Each property name in a type must be unique. Property name 'Address_Id' is already defined.
I do believe I'm mixing things up (with MapKey and the attributes of AddressId). However, I don't know how to fix this.
I went through this SO question and this article; however, no luck so far.
Link to DotNetFiddle. It won't work cause there is no database.
For some reason, it's not deleting the address when, student object is deleted.
That's the normal behavior for the relationship you have defined. It's not a matter of data annotations or fluent configuration. If you have different expectations, you'd better revisit your model.
Every relationship has a one side called principal and another side called dependent. The principal side (a.k.a. master, primary) is the one being referenced. The dependent side (a.k.a. detail, secondary) is the one that is referencing the principal. The foreign key is put on the dependent side and must always point to an existing principal or null when the relationship is optional. The cascade delete works by deleing all the dependent records when the principal record is deleted.
As explained in the How Code First Determines the Principal and Dependent Ends in an Association? section of the article mentioned by you, EF always uses the required side as principal and allows you to chose the one only when both are required.
With all that being said, let see what you have.
Address is required, Student is optional. Also you want to put FK in Student, i.e. Student references Address.
All that means that in your relationship, Address is the principal and Student is the dependent. Which means the Address may exists w/o Student referencing it. If cascade delete is turned on (as you did), deleting the Address will delete the Student, not the opposite.
I think all that should explain why it's is working the way it is now, and that no attributes or configuration can help to achieve what are you asking for. If you want it differently, the same article (and related from the same series) explains how to configure the relationship to either use Shared Primary Key Association or Foreign Key Association at the Address side. Whether it is unidirectional or bidirectional absolutely has nothing in common with the problem - see Should We Make the Associations Bidirectional? section in the article.
You foreign key should be like this :
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AddressId")]
public Address Address { get; set; }
[Column("Address_Id")]
public int AddressId { get; set; }
}
In your fluent mapping you just need:
modelBuilder.Entity<Student>()
.HasRequired(s => s.Address)
.WillCascadeOnDelete(true);
Or you can force cascade delete with an annotation:
[Required]
[ForeignKey("AddressId")]
public Address Address { get; set; }
Now update your database and your mapping should be correct and the delete should cascade.

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!

Why is EF code-first generating an extraneous foreign key column?

I'm using entity framework code-first to create my database schema automatically, and one of my entities looks like this:
public class AssessmentsCaseStudies {
#region Persisted fields
[Required]
[Key, Column(Order=0)]
[ForeignKey("Assessment")]
public int AssessmentId { get; set; }
[Required]
[Key, Column(Order=1)]
[ForeignKey("CaseStudy")]
public int CaseStudyId { get; set; }
[Required]
public int Score { get; set; }
[ForeignKey("Follows")]
public int? FollowsCaseStudyId { get; set; }
#endregion
#region Navigation properties
public virtual Assessment Assessment { get; set; }
public virtual CaseStudy CaseStudy { get; set; }
public virtual CaseStudy Follows { get; set; }
#endregion
}
When EF auto-generates my database, it generates a table with the following columns:
AssessmentId (PK, FK, int, not null)
CaseStudyId (PK, FK, int, not null)
Score (int, not null)
FollowsCaseStudyId (FK, int, null)
CaseStudy_CaseStudyId (FK, int, null)
This is all fine apart from the CaseStudy_CaseStudyId column. Why has that been generated? What is it for? How can I stop it being generated? My suspicion is that EF can no longer automatically match up CaseStudy's ICollection<AssessmentsCaseStudies> with the CaseStudyId column, so it creates its own column to link the two together for that navigation property.
Because you have two navigation properties of type CaseStudy in your AssessmentsCaseStudies entity and an AssessmentsCaseStudies collection in your CaseStudy entity EF cannot decide which of the two CaseStudy navigation properties this collection refers to. Both could be possible and both options would result in a valid but different entity model and database schema.
In such an ambiguous situation the EF convention is to create actually three relationships, i.e. your collection in CaseStudy does not refer to any of the two CaseStudy navigation properties but has a third (but not exposed and "invisible") endpoint in AssessmentsCaseStudies. This third relationship is the reason for the third foreign key your are seeing in the database - the one with the underscore. (The underscore is always a strong indication that something happend by mapping convention and not by your explicit configuration or data annotations.)
To fix the problem and to override the convention you can apply the [InverseProperty] attribute, thereby specifying the CaseStudy navigation property the AssessmentsCaseStudies collection belongs to:
[InverseProperty("AssessmentsCaseStudies")] // the collection in CaseStudy entity
public virtual CaseStudy CaseStudy { get; set; }
You can also (alternatively, you don't need both) put the attribute on the collection side:
[InverseProperty("CaseStudy")] // the CaseStudy property in AssessmentsCaseStudies entity
public virtual ICollection<AssessmentsCaseStudies> AssessmentsCaseStudies { get; set; }
For some reason, Slauma's InverseProperty attribute suggestion didn't work. What did work was me specifying the relationship between the two CaseStudy navigation properties in AssessmentsCaseStudies, and the CaseStudy entity, via the Fluent API in my database context's OnModelCreating method:
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasRequired(acs => acs.CaseStudy)
.WithMany(cs => cs.AssessmentsCaseStudies)
.HasForeignKey(acs => acs.CaseStudyId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasOptional(acs => acs.Follows)
.WithMany() // No reverse navigation property
.HasForeignKey(acs => acs.FollowsCaseStudy)
.WillCascadeOnDelete(false);
Once that's added, the migration code that's generated when I Add-Migration no longer tries to add the CaseStudy_CaseStudyId column and I just get the FollowsCaseStudyId column added, with the appropriate foreign key relationship.
For anyone else landing here looking for a solution, if you've tried the previous answers and are still getting an extra foreign key column, look for any properties you may have defined further down your POCO class that you did not intend to map to DB fields. Even if they contain code blocks, as with complex get accessors, Entity Framework will try to map them to the database somehow. This may result in extra foreign key columns if your properties return entities. To be safe, either decorate such properties with the [NotMapped] attribute or convert them to methods.

Categories

Resources