I am running into an interesting issue when trying to add an entity to the database with Entity Framework. When I try to add a new exception to the database, I run into the following error:
"Cannot insert explicit value for identity column in table 'Notification' when identity_insert is set to off"
I am guessing this error is caused due to the Notification entity already having an Identifier (Id property). However, my goal is not to store the Notification entity. It just so happens that my NotificationException entity has a reference to an existing Notification entity.
How can I update my NotificationException entities properly without running into this problem? Actually turning the identity_insert off does not seem like a viable solution.
My two model classes:
public class Notification
{
// Primary Key
public long Id { get; set; }
// Properties
public bool IsSent { get; set; }
public bool IsExpired { get; set; }
public int RetryCount { get; set; }
public int RetryTime { get; set; }
}
public class NotificationException
{
// Primary Key
public long Id { get; set; }
// Properties
public int Timestamp { get; set; }
public string Exception { get; set; }
// Foreign Keys
public long NotificationId { get; set; }
// Navigation Properties
public virtual Notification Notification { get; set; }
}
Entity Configuration with Fluent API:
private void ConfigureNotificationEntity(EntityTypeBuilder<Notification> builder)
{
builder.ToTable("Notifications");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasMany(n => n.Exceptions)
.WithOne(e => e.Notification)
.OnDelete(DeleteBehavior.Cascade);
}
private void ConfigureNotificationExceptionEntity(EntityTypeBuilder<NotificationException> builder)
{
builder.ToTable("NotificationExceptions");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasOne(i => i.Notification)
.WithMany(j => j.Exceptions);
}
The main problem:
public async Task<NotificationException> Add (NotificationException item)
{
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
As soon as the _context.SaveChangesAsync(); is called, the error mentioned above is thrown.
///Edit
I tested this issue with different objects as well. If the entity has no nested entities, then storing them works just fine. The issue is quite likely with the already known ID of the nested entity.
So it turns out in 2016, EF Core changed the behaviour of the Attach method from EF6: https://github.com/dotnet/efcore/issues/4424
to wit:
Add: Adds every reachable entity that is not already tracked
Attach: Attaches every reachable entity, except where a reachable entity has a store generated key and no key value is assigned, these will be marked as added.
Therefore, the solution is just to change
_context.NotificationExceptions.Add(item); to
_context.NotificationExceptions.Attach(item);
This will Add the new NotificationException pushed without a key; however the child Notification, with a declared key, will be attached as 'Unchanged'.
The problem looks like an issue with how you add notification object to your notificationException object.
Change your Add function like so:
public async Task<NotificationException> Add (NotificationException item)
{
var notification = _context.Notifications.Find( x => x.Id == yourNotificationId);
item.Notification = notification;
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
The trick here is we got notification entity from same context that we will add notificationException object.
Check this link for more info
Found a fix!
var exception = new NotificationException()
{
Exception = "Very serious exception!",
QueuedNotificationId = notification.Id,// <-- setting the foreign key
Timestamp = 420
};
Instead of referencing the entire Notification object within the Exception entity, I just set the foreign key. This way I can store the entity just fine. Still a bit silly how EF doesn't automatically recognize the Notification entity already exists.
Related
Using .NET Core 2.1, Entity Framework Core and NPGSQL.
I create a new record:
await _context.music.AddAsync(song);
await _context.SaveChangesAsync();
Then, within the same function, I do a few calculations (which I cannot do at the time of Creation) and then call Update (having updated the Comments field only):
_context.music.Update(song);
await _context.SaveChangesAsync();
Here is the class of "Song":
public class Song
{
[Key]
public int SongId { get; set; }
public int Title{ get; set; }
public string Comments{ get; set; }
//Navigation Properties
public virtual ICollection<Foo> Foos { get; set; }
}
In my MusicContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ForNpgsqlUseIdentityColumns();
modelBuilder.Entity<Foo>()
.HasOne(m => m.Song)
.WithMany(r => r.Foos)
.HasForeignKey(m => m.SongId);
}
This is the exception I get:
System.AggregateException: One or more errors occurred. (One or more
errors occurred. (The instance of entity type 'Song' cannot be
tracked because another instance with the same key value for
{'SongId'} is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the
conflicting key values.)) ---> System.AggregateException: One or more
errors occurred.
Anyone have any ideas? :-/
I would reconsider your approach. If you're in the same method doing an add then an update, I would just do the add at the bottom including the comment value from your calculations.
While I am trying to delete my entity I am getting following error in my function;
A relationship from the 'ProjectWebsiteTag_ProjectUser' AssociationSet is
in the 'Deleted' state.
Given multiplicity constraints, a corresponding 'ProjectWebsiteTag_ProjectUser_Target' must also in the 'Deleted' state.
Here is my code to delete;
public bool Delete(int id)
{
try
{
using (ProjectDataContext context = new ProjectDataContext())
{
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id);
context.WebsiteTags.Remove(websiteTag);
int saveChanges = context.SaveChanges();
return saveChanges > 0;
}
}
catch (DbEntityValidationException e)
{
FormattedDbEntityValidationException newException = new FormattedDbEntityValidationException(e);
throw newException;
}
}
Here is my data class;
public class ProjectWebsiteTag
{
public int WebsiteTagId { get; set; }
public ProjectUser ProjectUser { get; set; }
public ProjectWebsite ProjectWebsite { get; set; }
}
My Config Class;
public ProjectWebsiteTagConfiguration()
{
ToTable("ProjectWebsiteTags");
HasKey(p => p.WebsiteTagId);
HasRequired(p => p.ProjectUser).WithRequiredDependent().WillCascadeOnDelete(false);
HasRequired(p => p.ProjectWebsite).WithRequiredDependent().WillCascadeOnDelete(false);
}
It looks like it is trying to delete User record, but I do not want that.
I just want to delete "ProjectWebsiteTag" and that's it.
What I am missing here?
It isn't trying to delete the ProjectUser. It's trying to insert it. ProjectWebsiteTagConfiguration() is saying that the ProjectWebsiteTags table has a ProjectUser foreign key in it. When you call
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id)
websiteTag has a ProjectUser with an empty string for its UserId property. So either the record in the ProjectWebsiteTags table has an empty string for the foreign key, or EF is newing a ProjectUser (with an empty UserId) when you get from the context. Either way, EF isn't aware of the existence of a ProjectUser with an empty string id, so when you call SaveChanges() it tries to add it. It can't because the UserId field is required and empty.
I had to fix my problem by adding one-to-many relationship configuration in my User class.
It comes to a resolution of I need to add relationship hint on both sides of Configuration declarations.
public class ProjectUserConfiguration : EntityTypeConfiguration<ProjectUser>
{
public ProjectUserConfiguration()
{
ToTable("ProjectUsers");
HasKey(p => p.UserId);
HasMany(p=>p.ProjectWebsiteTags).WithRequired(q=>q.ProjectUser).WillCascadeOnDelete(false);
}
}
When I am trying to clear a collection (calling .Clear) I get the following exception:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
The inner exception is:
A relationship from the 'User_Availability' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'User_Availability_Target' must also in the 'Deleted' state.
User looks like this:
....
ICollection<Availability> Availability { get; set; }
Availability looks like this:
int ID { get; set; }
User User { get; set; }
DateTime Start { get; set;
DateTime End { get; set; }
Configuration is as follows:
HasMany(x => x.Availability).WithRequired(x => x.User);
HasRequired(x => x.User).WithMany(x => x.Availability);
The code causing the problem is:
user.Availability.Clear();
I've looked at other alternatives such as using the DbSet to remove items, but I don't feel my code will be as clean. Is there a way to accomplish this by clearing the collection?
The only way that I'm aware of to make it work is defining the relationship as an identifying relationship. It would required to introduce the foreign key from Availability to User as a foreign key into your model...
public int ID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
...and make it part of the primary key:
modelBuilder.Entity<Availability>()
.HasKey(a => new { a.ID, a.UserID });
You can extend your mapping to include this foreign key (just to be explicit, it isn't required because EF will recognize it by convention):
modelBuilder.Entity<Availability>()
.HasRequired(a => a.User)
.WithMany(u => u.Availability)
.HasForeignKey(a => a.UserID);
(BTW: You need to configure the relationship only from one side. It is not required to have both these mappings in your question.)
Now you can clear the collection with user.Availability.Clear(); and the Availability entities will be deleted from the database.
There is one trick. You can delete entities without using special DbSet:
(this.dataContext as IObjectContextAdapter).ObjectContext.DeleteObject(entity);
Execute this for each item in Availability collection before clearing it. You don't need 'identifying relationships' for this way.
In case someone has the same problem using SQLite:
Unfortunately the accepted answer does not work with SQLite because SQLite does not support auto increment for composite keys.
You can also override the SaveChanges() Method in the Database context to delete the children:
//// Long Version
//var localChilds = this.SubCategories.Local.ToList();
//var deletedChilds = localChilds.Where(w => w.Category == null).ToList();
//foreach(var child in deletedChilds) {
// this.SubCategories.Remove(child);
//}
// Short in LINQ
this.SubCategories.Local
.Where(w => w.Category == null).ToList()
.ForEach(fe => this.SubCategories.Remove(fe));
#endregion
See this great Blogpost as my source (Unfortunately written in german).
For an application using Code First EF 5 beta I have:
public class ParentObject
{
public int Id {get; set;}
public virtual List<ChildObject> ChildObjects {get; set;}
//Other members
}
and
public class ChildObject
{
public int Id {get; set;}
public int ParentObjectId {get; set;}
//Other members
}
The relevant CRUD operations are performed by repositories, where necessary.
In
OnModelCreating(DbModelBuilder modelBuilder)
I have set them up:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithOptional()
.HasForeignKey(c => c.ParentObjectId)
.WillCascadeOnDelete();
So if a ParentObject is deleted, its ChildObjects are too.
However, if I run:
parentObject.ChildObjects.Clear();
_parentObjectRepository.SaveChanges(); //this repository uses the context
I get the 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.
This makes sense as the definition of the entities includes the foreign key constraint which is being broken.
Can I configure the entity to "clear itself up" when it gets orphaned or must I manually remove these ChildObjects from the context (in this case using a ChildObjectRepository).
It is actually supported but only when you use Identifying relation. It works with code first as well. You just need to define complex key for your ChildObject containing both Id and ParentObjectId:
modelBuilder.Entity<ChildObject>()
.HasKey(c => new {c.Id, c.ParentObjectId});
Because defining such key will remove default convention for auto incremented Id you must redefine it manually:
modelBuilder.Entity<ChildObject>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Now calling to parentObject.ChildObjects.Clear() deletes dependent objects.
Btw. your relation mapping should use WithRequired to follow your real classes because if FK is not nullable, it is not optional:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithRequired()
.HasForeignKey(c => c.ParentObjectId)
.WillCascadeOnDelete();
Update:
I found a way that doesn't need to add navigational properties from the child to the parent entity or to set up a complex key.
It's based on this article which uses the ObjectStateManager to find the deleted entities.
With a list ObjectStateEntry in hand, we can find a pair of EntityKey from each, which represents the relationship that was deleted.
At this point, I couldn't find any indication of which one had to be deleted. And contrary to the article's example, simply picking the second one would get the parent deleted in cases where the child had a navigation property back to the parent. So, in order to fix that, I track which types should be handled with the class OrphansToHandle.
The Model:
public class ParentObject
{
public int Id { get; set; }
public virtual ICollection<ChildObject> ChildObjects { get; set; }
public ParentObject()
{
ChildObjects = new List<ChildObject>();
}
}
public class ChildObject
{
public int Id { get; set; }
}
The other classes:
public class MyContext : DbContext
{
private readonly OrphansToHandle OrphansToHandle;
public DbSet<ParentObject> ParentObject { get; set; }
public MyContext()
{
OrphansToHandle = new OrphansToHandle();
OrphansToHandle.Add<ChildObject, ParentObject>();
}
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.DetectChanges();
var deletedThings = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
foreach (var deletedThing in deletedThings)
{
if (deletedThing.IsRelationship)
{
var entityToDelete = IdentifyEntityToDelete(objectContext, deletedThing);
if (entityToDelete != null)
{
objectContext.DeleteObject(entityToDelete);
}
}
}
}
private object IdentifyEntityToDelete(ObjectContext objectContext, ObjectStateEntry deletedThing)
{
// The order is not guaranteed, we have to find which one has to be deleted
var entityKeyOne = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[0]);
var entityKeyTwo = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[1]);
foreach (var item in OrphansToHandle.List)
{
if (IsInstanceOf(entityKeyOne, item.ChildToDelete) && IsInstanceOf(entityKeyTwo, item.Parent))
{
return entityKeyOne;
}
if (IsInstanceOf(entityKeyOne, item.Parent) && IsInstanceOf(entityKeyTwo, item.ChildToDelete))
{
return entityKeyTwo;
}
}
return null;
}
private bool IsInstanceOf(object obj, Type type)
{
// Sometimes it's a plain class, sometimes it's a DynamicProxy, we check for both.
return
type == obj.GetType() ||
(
obj.GetType().Namespace == "System.Data.Entity.DynamicProxies" &&
type == obj.GetType().BaseType
);
}
}
public class OrphansToHandle
{
public IList<EntityPairDto> List { get; private set; }
public OrphansToHandle()
{
List = new List<EntityPairDto>();
}
public void Add<TChildObjectToDelete, TParentObject>()
{
List.Add(new EntityPairDto() { ChildToDelete = typeof(TChildObjectToDelete), Parent = typeof(TParentObject) });
}
}
public class EntityPairDto
{
public Type ChildToDelete { get; set; }
public Type Parent { get; set; }
}
Original Answer
To solve this problem without setting up a complex key, you can override the SaveChanges of your DbContext, but then use ChangeTracker to avoid accessing the database in order to find orphan objects.
First add a navigation property to the ChildObject (you can keep int ParentObjectId property if you want, it works either way):
public class ParentObject
{
public int Id { get; set; }
public virtual List<ChildObject> ChildObjects { get; set; }
}
public class ChildObject
{
public int Id { get; set; }
public virtual ParentObject ParentObject { get; set; }
}
Then look for orphan objects using ChangeTracker:
public class MyContext : DbContext
{
//...
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var orphanedEntities =
ChangeTracker.Entries()
.Where(x => x.Entity.GetType().BaseType == typeof(ChildObject))
.Select(x => ((ChildObject)x.Entity))
.Where(x => x.ParentObject == null)
.ToList();
Set<ChildObject>().RemoveRange(orphanedEntities);
}
}
Your configuration becomes:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithRequired(c => c.ParentObject)
.WillCascadeOnDelete();
I did a simple speed test iterating 10.000 times. With HandleOrphans() enabled it took 1:01.443 min to complete, with it disabled it was 0:59.326 min (both are an average of three runs). Test code below.
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Add(new ChildObject());
context.SaveChanges();
}
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Clear();
context.SaveChanges();
}
Want to share another .net ef core solution that worked for me, may be somebody will find it usefull.
I had a child table with two foreign keys (either or), so the accepted solution didn't work for me. Based on the answer by Marcos Dimitrio I came up with the following:
In my custom DbContext:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
var modifiedEntities = this.ChangeTracker.Entries().Where(c => c.State == EntityState.Modified);
foreach (var entityEntry in modifiedEntities)
{
if (entityEntry.Entity is ChildObject)
{
var fkProperty = entityEntry.Property(nameof(ChildObject.ParentObjectId));
if (fkProperty.IsModified && fkProperty.CurrentValue == null && fkProperty.OriginalValue != null)
{
// Checked if FK was set to NULL
entityEntry.State = EntityState.Deleted;
}
}
}
return await base.SaveChangesAsync(cancellationToken);
}
In EF Core, it can be done by Delete Orphans.
Like this:
dbContext.Children.Clear();
This is my generic solution for Entity Framework 6.4.4, without knowledge of the particular schema.
Note that I start my search for orphan entities from modified entity entries, as in my case I could not find anything searching for deleted relationship entries like other answers suggest.
The logic behind the approach is that an entity removed from a collection of a required relationship will have its foreign key updated to null by the Entity Framework. So we search for all modified entities which have at least one relationship to an end with multiplicity 'One' but having the foreign key set to null.
Add this method to your DbContext subclass. You could override the SaveChanges / SaveChangesAsync methods to call this method automatically.
public void DeleteOrphanEntries()
{
this.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var orphanEntityEntries =
from entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
where !entry.IsRelationship
let relationshipManager = entry.RelationshipManager
let orphanRelatedEnds = from relatedEnd in relationshipManager.GetAllRelatedEnds().OfType<EntityReference>()
where relatedEnd.EntityKey == null // No foreign key...
let associationSet = (AssociationSet)relatedEnd.RelationshipSet
let associationEndMembers = from associationSetEnd in associationSet.AssociationSetEnds
where associationSetEnd.EntitySet != entry.EntitySet // ... not the end pointing to the entry
select associationSetEnd.CorrespondingAssociationEndMember
where associationEndMembers.Any(e => e.RelationshipMultiplicity == RelationshipMultiplicity.One) // ..but foreign key required.
select relatedEnd
where orphanRelatedEnds.Any()
select entry;
foreach (var orphanEntityEntry in orphanEntityEntries)
{
orphanEntityEntry.Delete();
}
}
Yes. The following works in EF Core:
Make sure you set the cascade behavior to Cascade like so:
entity.HasOne(d => d.Parent)
.WithMany(p => p.Children)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.Cascade);
Then set the Parent property to be null in all the child entities that are to be deleted like so:
var childrenToBeRemoved = parent.Children.Where(filter);
foreach(var child in childrenToBeRemoved)
{
child.Parent = null;
}
Now, context.SaveAsync() should delete all the orphaned children entities.
This is not something that is supported automatically by EF right now. You can do it by overriding SaveChanges in your context and manually deleting an child objects that no longer have a parent. The code would be something like this:
public override int SaveChanges()
{
foreach (var bar in Bars.Local.ToList())
{
if (bar.Foo == null)
{
Bars.Remove(bar);
}
}
return base.SaveChanges();
}
Say I have two entities like so:
public class Response
{
public int Id { get; set; }
public int PatientId { get; set; }
public virtual Patient Patient { get; set; }
public string Text { get; set; }
}
public class Patient
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Response> Responses { get; set; }
}
I want to be able to call
Patient.Responses.Remove(someResponse);
And have entity delete not only the relationship but the Response entity as well. At present if I just delete the relationship I get the following error:
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.
Reading this blog post http://blogs.msdn.com/b/dsimmons/archive/2010/01/31/deleting-foreign-key-relationships-in-ef4.aspx I realised I can achieve this by having the following mapping:
modelBuilder.Entity<Response>().HasKey(m => new { m.Id, m.PatientId });
But I don't want to change my primary key. What I want to do is override DbContext.SaveChanges() and mark for deletion any Responses where the Patient relationship has been deleted. I tried this:
public override int SaveChanges()
{
// Need to manually delete all responses that have been removed from the patient, otherwise they'll be orphaned.
var orphanedResponses = ChangeTracker.Entries().Where(
e => e.State == EntityState.Modified &&
e.Entity is Response &&
e.Reference("Patient").CurrentValue == null);
foreach (var orphanedResponse in orphanedResponses)
{
Responses.Remove(orphanedResponse.Entity as Response);
}
return base.SaveChanges();
}
But I found it's possible to attach a Response with only Response.PatientId set and not Response.Patient, entity wont have loaded the Response.Patient property so my code thinks it's been orphaned and should be deleted.
In summary
What I want to know is how can I can tell that an entity has been modified because it's FK relationship has been removed.
Use this instead:
public override int SaveChanges()
{
var responses = Responses.Local.Where(r => r.Patient == null);
foreach (var response in responses.ToList())
{
Responses.Remove(response);
}
return base.SaveChanges();
}
You need to configure the mappings such that a cascade delete will occur. To do that you need to map the model with WillCascadeOnDelete to true.
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Patient>()
.HasMany(patient=> patient.Responses)
.WithRequired(response => response.Patient)
.HasForeignKey(response => response.PatientId)
.WillCascadeOnDelete(true);
}
}
I think my problem is not with the code but rather with how I assume entity's Attach() method works. I assumed that if I attach a response with PatientId set but not Patient property then entity would populate the Patient property for me.
In fact what I think happens is entity attaches it as it is, then if I mark that entity as modified and save it, entity sees the null Patient property and assumes I want to remove the relationship, so throws an error because it would be orphaned (can't null Response.PatientId). So perhaps everything is working as designed and my SaveChanges() solution works.