DbSet.Attach attempting to insert explicit value for generated identity column - c#

I have the following classes:
public class MyEntity
{
public int Id { get; set; }
}
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public DbSet<MyEntity> MyEntities { get; set; }
}
Then in a separate class I am running the following:
var entity = new MyEntity();
myContext.MyEntities.Attach(entity).State = EntityState.Added;
myContext.SaveChanges();
The call to SaveChanges throws the exception:
Cannot insert explicit value for identity column in table MyEntity when IDENTITY_INSERT is set to OFF
At this point my schema is being created entirely by convention, and the Id column is supposed to be auto-generated. The problem seems to have something to do with my using Attach and setting EntityState.Added manually. When I instead use Add like so:
myContext.MyEntities.Add(entity);
It works just fine. However, I want to use Attach because in my real scenario MyEntity will have untracked child properties that already exist in the database. Add automatically sets the state of related untracked entities to EntityState.Added, and I don't want to have to manually set the state of those existing entities to EntityState.Unchanged.
What am I missing here? Why is EF attempting to insert an explicit identity value, even after attaching the entity and setting its state to Added?

This is a by-design behavior in EF.
If you call Add(), it means that the object is not present on the database, so a key is generated for that entity.
If you call Attach(), it means that it is present in the db, so no key generation is performed, even if you set the state to added later.

Related

EF Core is attempting to update a record that doesn't exist

UPDATE: There was code that I did not see that until after I created this post that was causing my insert to fail. All works as expected now. Apologies, and thank you for the time spent helping.
I am having an issue where EF is attempting to update a record that doesn't exist. I need the record to be inserted.
Sample code below:
DoStuff(List<ParentObj> listParent, List<OtherParent> listOtherParent)
{
foreach(var op in otherParent)
{
var updateThisParentRecord = listParent.FirstOrDefault(x => x.Id == op.Id);
if(updateThisParentRecord != null)
{
updateThisParentRecord.ChildRecordList.Add(new ChildRecord
{
//set relevant props not PK as it is an identity column
OtherChildObject = new OtherChildObject
{
//set relevant props not PK as it is an identity column
}
});
}
}
await _parentObjectContext.SaveChangesAsync();
}
Model code:
public partial class ParentObj
{
public ParentObj()
{
ChiledRecordList = new HashSet<ChildRecord>();
}
public int Id {get;set;}
public virtual ICollection<ChildRecord> ChildRecordList {get;set;}
}
public partial class ChildRecord
{
public int Id {get;set;}
public int ParentId {get;set;}
public int OtherChildObject {get;set;}
public virtual OtherChildObject OtherChildObject {get;set;}
public virtual ParentObj {get;set;}
}
public partial class OtherChildObject
{
public OtherChildObj()
{
ParentObj = new HashSet<ParentObj>();
ChildRecord = new Hashset<ChildRecord>();
}
public long Id {get;set;}
//now that I have written this out, the below line seems strange and may
//be keyed wrong?
public virtual ICollection<ParentObj> ParentObj {get;set;}
public virtual ICollection<ChildRecord> ChildRecord {get;set;}
}
When saving the below exception is thrown:
"Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded.
See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions."
This is what is in the entry list of the exception: {{Id: 1000} Modified EntityType: ChildRecord}
The generated SQL from EF rightfully creates OtherChildObject but it is attempting to update the ChildRecord which doesn't exist. Does anyone know what is going on? Thanks in advance
Breaking change from DetectChanges honors store-generated key values:
Old behavior
Before EF Core 3.0, an untracked entity found by DetectChanges would
be tracked in the Added state and inserted as a new row when
SaveChanges is called.
New behavior
Starting with EF Core 3.0, if an entity is using generated key values
and some key value is set, then the entity will be tracked in the
Modified state. This means that a row for the entity is assumed to
exist and it will be updated when SaveChanges is called. If the key
value isn't set, or if the entity type isn't using generated keys,
then the new entity will still be tracked as Added as in previous
versions.
Why
This change was made to make it easier and more consistent to work
with disconnected entity graphs while using store-generated keys.
Mitigations
This change can break an application if an entity type is configured
to use generated keys but key values are explicitly set for new
instances. The fix is to explicitly configure the key properties to
not use generated values. For example, with the fluent API:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.ValueGeneratedNever();
Or with data annotations:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
If you are explicitly setting the Id of your ChildObjects and the entity is using generated key values then they will be tracked in the Modified state.

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.

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.

NHibernate Guid.Comb does Insert followed by Update

I have the following entity:
public class Entity
{
public virtual Guid Id { get; protected set; }
public virtual String Name { get; set; }
}
With the following Mapping:
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable();
}
}
I get an error executing the following code:
using(var tx = Session.BeginTransaction())
{
var entity = GetSomethingFromTheDatabase();
if(entity == null)
{
Session.Save(new Entity());
}
entity.Name = "test";
tx.Commit();
}
The error is:
NHibernate.PropertyValueException : not-null property references a null or transient value Entity.Name
When I make the property Name nullable, everything works fine, but NHibernate issues an Insert-statement (with Name=null) followed by an Update-statement (with Name='test')
So: Why does NHibernate try to insert my entity before updating it with the values? That's how the autoincrement id-generator works. We try to avoid this with Guid.Comb. If I read the documentation, Guid.Comb should generate the ID without going to the database.
EDIT:
I clarified the code a bit to show what we want to do. We want to insert an Entity when it is not present in the database.
The database statements are only executed when the transaction is committed. So when Save() is called, nothing happens. But when tx.Commit() is called, the insert is issued and immediately followed by the update. I would think that NHibernate would save all changed values and put them in one Insert.
Is it issuing the inserts as soon as .Save is executed?
Maybe you should set the entity.Name before you issue the Save.
I've always had the strong opinion that if you are creating your entity the constructor should take all of the required fields to construct the object. A field in the database that is not nullable is a required field.
Because you Save it before you modify it? When you call .Save the entity gets validated and if needed persisted or else waits before it either gets flushed or the transaction is completed. When you call Save it has a property with a null value and so you get the exception.
Console.WriteLine(session.Save(..))
Returns the generated ID for your entity. You could see if that value corresponds with the value in the database.
But I would indeed assume that no roundtrip would be made to the database yet until either flushed or committed.

Entity Framework - default values doesn't set in sql server table

SQL server 2005 database table has a column 'createdon' for which default value set to getdate(). I am trying to add a record using entity framework. 'createdon' column is not getting updated.
Did I miss any property in Entity framework, please suggest.
This is one of the few issues that are problematic with Entity Framework. Say you have a class that looks like this:
public class MyEntity
{
// Id is a PK on the table with Auto-Increment
public int Id { get; set; }
// CreatedOn is a datetime, with a default value
public DateTime CreatedOn { get; set; }
}
Now, you want to insert a new element:
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity())
}
Entity Framework knows how to handle an auto-increment primary key because of the definition in the EDMX. It will not try to insert a value for the Id property. However, as far as Entity Framework is concerned, CreatedOn has a value: the default DateTime. Because Entity Framework cannot say "well, it has a value but I should ignore it", it will actively insert the record with the CreatedOn property value, bypassing the default value on your column definition on your table.
There is no easy way to do this. You can either actively set the CreatedOn property to DateTime.Now when you insert that item. Or you can create an interface and an extension method pair:
public interface ICreatedOn
{
public DateTime CreatedOn { get; set; }
}
public partial class MyEntity : ICreatedOn
{
}
public static TEntity AsNew<TEntity>(this TEntity entity) where TEntity : ICreatedOn
{
if(entity != null)
entity.CreatedOn = DateTime.Now;
return entity;
}
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity().AsNew())
}
Edit: To expand on this point, the reason why this is an unresolvable issue is because of the meaning behind an autoincrement field and a field with a default value constraint. An auto-increment field should, by definition, always be handle by the server, using a seed and all that jazz. You cannot specify a value for an auto-increment field on an insert unless you have used SET IDENTITY INSERT ON. A default value, however, is just a hint that say "if I don't specify any value, use this". Because value types in .NET cannot be null, there will always be a value and Entity Framework cannot infer that the default value for that field, at that time, means that you want it to be defaulted on the SQL server.
Next to using the designer and some more nifty stuff shown already, you can also mark the columns as being calculated by simply setting the DatabaseGenerated attribute on the field:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; set; }
You can set StoreGeneratedPattern to Computed (as Malcolm suggested) in the GUI of the entity data model as well.
Open your .edmx file in Visual Studio
Open the properties of the field (click on the field -> hit
F4 or right click->properties)
Set StoreGeneratedPattern to Computed in the properties window
as shown below:
I've got around this issue by telling EF that the column is 'computed', and should therefore be left alone for inserts.
If you look in the configuration for the generated entity
namespace Data.Context
{
// Table
internal partial class MyTableConfiguration : EntityTypeConfiguration<MyTable>
{
public MyTableConfiguration(string schema = "dbo")
{
ToTable(schema + ".MyTable");
HasKey(x => x.Id);
Property(x => x.ColumnName).HasColumnName("ColumnName").IsOptional().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
....

Categories

Resources