I have two tables, which have a 1:n relationship. I used the following EF Code First relationship definition:
modelBuilder.Entity<MyPrimary>()
.HasMany(x => x.MyOthers)
.WithRequired()
.HasForeignKey(x => x.primary_id)
.WillCascadeOnDelete();
Note that primary_id is a non-nullable column, that's also why I set the relationship to .WithRequired() - a MyOther needs to have a MyPrimary and cannot exist on its own.
Now I have the following code:
myPrimary.MyOthers.Clear();
ctx.SaveChanges();
And I receive the following exception:
System.InvalidOperationException : 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.
Why? Shouldn't all the MyOther instances in myPrimary.MyOthers be cascade-deleted and therefore the non-nullable FK should not be a problem?
When you call .Clear() EF trying to unassign MyOther from MyPrimary, It is not smart enough to figure out that you want to delete MyOther records. Maybe after clear you will decide to add these MyOther records to another MyPrimary record. So you should to mark these records as deleted explicitly. You can write method similar to this
public void MarkForDeleteItems<T>(ICollection<T> collection) where T : class
{
foreach (var collectionItem in collection.ToList())
{
ctx.Entry(collectionItem).State = EntityState.Deleted;
}
}
And then use it
MarkForDeleteItems(myPrimary.MyOthers);
ctx.SaveChanges();
Also WillCascadeOnDelete meaning that when you delete MyPrimary record than database will delete all MyOthers records which related to MyPrimary.
Related
Im using EF 6 to build my app. Ive got entity User and entity BoughtProduct
In User:
this.BoughtProducts = new HashSet<BoughtProduct>();
Then I do some logic in my app. Sometimes I do sth like this:
MyUser.BoughtProducts.Clear();
then when I save changes to my context I have following error:
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.
What am I doing wrong?
I'm using EF Code first. a simple model:
item { public int Id {set; get;},... ,ICollection<ItemImages> {set; get;} }
itemImages {
public int Id {set; get; },
public int ItemId {set; get; }
, ... ,
public Item Item {set; get; }
}
ItemConfig:EntityTypeConfiguration<Item>
{
//some config statement;
//...
// mark child delete when parent delete: waterfall delete.
HasRequired(rs => rs.ItemCat).WithMany(rs => rs.Items).HasForeignKey(rs => rs.ItemCatId).WillCascadeOnDelete(true);
}
when delete entity by Remove(), it delete item and related child (item images records) well.
_db.Item.Remove(DeleteThisObj);
_db.SaveChanges();
but when mark it to delete:
_db.Entry(DeleteThisObj).State = EntityState.Deleted;
_db.SaveChanges();
get 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 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.
If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place). So just use Remove()
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
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.
A thing worth noting is that setting .State = EntityState.Deleted does not trigger automatically detected change.
I have a table named "Notaries":
NotaryID int,
NotaryName nvarchar(MAX)
and table named "NotaryPhones":
PhoneID int,
NotaryID int,
PhoneNumber nvarchar(50)
So, relationship "one-to-many". Now I want to clear all phones, depending on the notary. My code:
Notary.Models.Notary notary = (from i in db.Notaries where i.NotaryID == model.NotaryID.Value select i).FirstOrDefault();
notary.CityID = Convert.ToInt32(model.City.SelectedItem);
notary.NotaryPhones.Clear();
db.SaveChanges();
but I get 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 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.
If I remove the string
notary.NotaryPhones.Clear();
it works. I understand, that this is a trivial thing, but don't understand how to fix it
What's going on is that your notary.NotaryPhones.Clear(); is removing the foreign-key reference from your NotaryPhone table to your Notary table. Because this is set up as a non-nullable key so that you don't have orphan phone records, you're receiving that error.
What you'll want to do instead is set up a method in your repository that will call context.NotaryPhones.Remove(**instance variable pointing to one of the phones to delete**); and that will delete them from the database.
CLARIFICATION
The reason why it removes the foreign key reference is that notary.NotaryPhones.Clear() just removes those objects from the notary object. The objects will continue to exist the way that you have written this.
So when the .Clear() is executed, it takes your phone.NotaryID property and gets rid of the ID pointer to the notary. Since you're not assigning it anything else, the value it tries to assign is null (the only value that couldn't possibly point to a Notary object). But, because you have it set up as an int, and not an int?, it can't make that assignment and it throws the error you see.
You said you're trying to delete the objects, so you need to remove them from the database. To do that, you do what I explained above.
please excuse the example if there are syntax errors, I am currently working in VB but the approach is the same
foreach (NotaryPhones np in notary.NotaryPhones)
{
db.NotaryPhones.DeleteObject(np);
}
then save your changes with
db.SaveChanges();
Clear just removes entity from the related collection. it doesn't remove entity from database. you have to delete each NotaryPhone from corresponding DbSet.
For some reason I had my made my mind a while back on an EF 6 project that I would try to avoid naming foreign keys. I defined much of the model without testing it incrementally and so I have been running into multiplicity and incomplete Fluent API definition issues:
A relationship from the 'User_InternalAuth' AssociationSet is in the
'Deleted' state. Given multiplicity constraints, a corresponding
'User_InternalAuth_Target' must also in the 'Deleted' state.
In one case, here is the code:
nModelBuilder.Entity<User>()
.HasOptional<InternalAuth>(u => u.InternalAuth)
.WithRequired(a => a.User)
.WillCascadeOnDelete(true);
My understanding is that it is saying:
The entity User
Has an optional property InternalAuth of type InternalAuth
On the other end, InternalAuth has a required property User, so that all InternalAuths have Users but Users may or may not have an `InternalAuth.
If the User gets deleted, so does his InternalAuth if he has one (does this override an optional behavior of treating optionals like nullables?)
However when I try to delete a User I receive an exception about the multiplicity of some association between InternalAuth and User.
Is it true that if EF understands the multiplicity of a relationship there is a way for it to provide it a unique column name for it so there is a canonical naming convention?
If so, do you ever really need to define foreign keys explicitly by annotating the model or through Fluent API?
If not, is it a worthwhile or advisable thing that I should keep trying to avoid it? (I'm thinking along the lines of migrating the data model, database administration, any EF quirks)
Why does attempting to delete the relationship above violate a multiplicity constraint? What else does it need to know?
assuming that
You can configure cascade delete on a relationship by using the WillCascadeOnDelete method. If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null.
My guess is the following : the FK is nullable so the fact to set it to null with the required constraint causes the rise of the exception.
One solution is to put the FK in the PK, that is add, in InternalAuth, the FK to User in the PK. Doing this will mark the entity as deleted when setting a part of his PK to null.
Im dealing with code first .NET 4 and i'm having trouble with a 1 to 1 relation.
breifing of database:
-POccurrence
-Id
-POccurrenceRiskAssessment
-OccurrenceId
in my class Poccurrence I have a property named RiskAsessment, of the type POccurrenceRiskAssessment. Not all POccurrences have riskassessments, so it needs to be nullable.
I tried
modelBuilder.Entity<POccurrence>().HasOptional(item => item.RiskAssessment).HasConstraint((o, r) => r.OccurrenceId == o.Id);
but that gives me
The navigation property
'RiskAssessment' declared on type
'AM.Pris.Classes.POccurrence' has been
configured as optional. Based on a
declared constraint, the navigation
property is required. Either make some
dependent key property nullable or
configure the navigation as required.
and if i try
modelBuilder.Entity<POccurrence>().HasRequired(item => item.RiskAssessment).HasConstraint((o, r) => r.OccurrenceId == o.Id);
i get
A referential integrity constraint
violation occurred: A primary key
property that is a part of referential
integrity constraint cannot be changed
when the dependent object is Unchanged
unless it is being set to the
association's principal object. The
principal object must be tracked and
not marked for deletion.
and i have no idea what to do. I even tried to delete the real relation in the DB but nothing seems to make any difference. Any idea? I guess its the first try with HasOptional i'm looking for, but how do i make it nullalbe?
Have you considered rolling this up into an Table Per Type inheritance scenario where POccurrenceRiskAssessment : POccurrence? That way you only need query POccurrenceRiskAssessment.