NHibernate Guid.Comb does Insert followed by Update - c#

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.

Related

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.

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

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.

Entity Framework validation fails on unused property update

I have a problem that I can't seem to understand, let alone solve. Any help would be appreciated.
So I have an MVC application, Entity Framework, the usual. I have a request that can update one property of one entity. This request sends the Id of the entity and the new value of the property.
In order to avoid querying the database for the entity, and since I only want to do the update on that one property and then return, I attach a new entity to the context with the id and then set the CurrentValue. Something like this:
public class MyEntityHandler
{
// dbContext is my EF context, instantiated from a DI container
// ...
public void UpdateProperty<TProp>(MyEntity entity, Expression<Func<MyEntity , TProp>> property, TProp value)
{
var memberName=(property.Body as MemberExpression).Member.Name;
var temp = dbContext.ChangeTracker.Entries<MyEntity>()
.SingleOrDefault( o => o.Entity.Id == entity.Id );
if( temp == null || temp.State == EntityState.Detached)
{
dbContext.MyEntities.Attach( entity );
}
this.dbContext.Entry(entity).Property(memberName).IsModified = true;
this.dbContext.Entry(entity).Property(memberName).CurrentValue = value;
}
}
Then, this can be used like this: UpdateProperty(e, e=>e.Prop, "NewValue");
where e is of type MyEntity, and Prop is a string property in it.
When the post request comes in, I simply create a new MyEntity, I assign the id and then call this method. Something like this (there are a couple more layers, but it makes no difference for my question):
public ActionResult MyMethod(int id, string newValue)
{
var e=new MyEntity { MyEntityId=id };
new MyEntityHandler().UpdateProperty(e,e=>e.Prop,newValue);
return View();
}
My understanding is that in this case, EF should attach the entity in unchanged state. The entity in my case is not in the context and the attach succeeds. Then I update that one single property, which will put the entity into Modified state, so an update statement should be generated on SaveChanges(). Since only the one property is changed, that should be the only one in the update statement. I double checked the values in the ChangeTracker, I can see that the entity is modified and the property is modified, but every other property i not modified.
The problem is that when I call SaveChanges(), I get a DbValidationException, because one of the other properties is null, but it has a Required attribute. This is all rightfully so - since I attach a new entity and set only the id before attaching, it should be null. I just don't understand the validation error - I'm not trying to insert that value into the database (again, I checked the ChangeTracker and the state of the entity is modified and the property in question is not modified).
Why do I get this validation error? Is this by design? Is there a way to tell EF to let this save through (without disabling the validation altogerher on the context)?

Unable to insert an explicit value in the EF Core identity column [duplicate]

I'm getting this error on EF.
Cannot insert explicit value for identity column in table
'GroupMembers_New' when IDENTITY_INSERT is set to OFF.
The column on the Db is identity increment and on the EF design file, StoreGeneratedPattern is identity as well. Seems like EF is trying to insert 0 every time I try to save.
Some suggestions says ID is reserved on tables or drop the table and rerun the scripts.
Any ideas?
Here's some code:
GroupMember groupMember = new GroupMember();
groupMember.GroupId = group.Id;
groupMember.UserId = (new UserId(group.Owner));
//groupMember.Id = _groupContext.GroupMembers.Count();
group.GroupMembers.Add(groupMember);
_groupContext.SaveChanges();
I have run into this before. This error means you are trying to assign a value explicitly to a column where the database automatically assigns it.
Suggestion:
Update your edmx file to reflect any changes you may have made in the database.
If the database automatically assigns the value, you should see the "IsDbGenerated=true" attribute in your designer file under that property. If it's not there, you can add it manually.
Try this:
using System.ComponentModel.DataAnnotations.Schema;
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public decimal Identity_Col { get; set; }
The Entity Framework class file adds these lines of code to the Identity column.
Put these attribs on top of the property which is identity:
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
I encountered the same problem and error message in my AspNetCore 2.x application.
The only way I could solve it was by removing this line in the ModelBuilder.Entity method of the DbContext class:
// remove: entity.Property(e => e.Id).ValueGeneratedNever();
EF Code first: Because of an auto-increment PK 'id' field AND a guid column, design like this:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid FileToken { get; set; }
there was a duplicate identity. I changed it to:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DefaultValue("newid()")]
public Guid FileToken { get; set; }
and the problem went away.
In EF 6, there is a property of the field/column in your model for doing this:
StoreGeneratedPattern.
Set this to "Identity" in the property dropdown list.
(I don't know about EF 4. The above answer, using IsDbGenerated, seems to be for EF 4.)
And this corresponds in the underlying XML to an attribute to the element:
<Property Name="MyTableId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
--but you don't need to deal with the XML manually, since you can use the designer.
How this gets messed up isn't clear. I had the problem even after refreshing my model from the database. Perhaps it gets confused if you set the PK on the table, or change its name, after you have already generated the model. (I am using the table/database-first approach, not code first.)
You can't use the above approach of putting the C# attribute on the entity code, because in this situation the entity code is generated by EF. EF is supposed to understand ("by itself") that the field is an identity.
I had this issue in my app; and got fixed it changing the property "StoredGeneratedPattern" of the id field to Identity.
So, Go to the model; look up for the table; click on propierties of the primary key fiel; and change the property.
See intercepting Entity Insert for generated always columns like StartTime and EndTime columns on history tables, rowversion columns as well.
I solved this by removing primary key in model from inserting data. because primary key auto increment.
var book = new Book
{
// Id = 1, //Don't need to write this
Genre = "Technology",
Author = "Charles Petzold",
Title = "Programming Windows 5th Edition",
Price = 30,
Publisher = "Microsoft Press"
};
_unitOfWork.Books.Add(book);
Well, You need give a value to ID, for example for the object Auto, just you should VarAuto.Id = 0;
After that you could do it something like this =>
using( MyContext db = new MyContext()){
db.Autos.Add(VarAuto);
db.SaveChanges();
}
That is the solution just give value to id, EF could be recognize the identity value in the table.
Just Try.
I'm using DB first and the table has identity column. I didn't use the db-scaffolding to generate this, I copied it from another entity and by mistake I took this property with.
So
Try to check the DBContext Class. I got this error, and the issue was with this property ".ValueGeneratedNever()"
I have just removed it and it works fine,
modelBuilder.Entity<TableName>(entity =>
{
entity.Property(e => e.Id)
//.ValueGeneratedNever()
.HasColumnName("ID");
});
Note: a moderator deleted this answer as a duplicate and left my other answer up, on a question with only the sql-server tag (which was the first question I arrived at from google). Since this question has the entity framework tag, posting the answer again here.
This is for EntityFramework Core 3.1.22. Using the wrong property to specify a foreign key causes Entity Framework to demote the primary key to ... something else. Entity Framework will then always attempt to insert an explicit value, which throws a database exception because it can't insert the value it's been told is a primary key and shouldn't be inserted.
Microsoft.EntityFrameworkCore.DbUpdateException: 'An error occurred while updating the entries. See the inner exception for details.'
Inner Exception:
SqlException: Cannot insert explicit value for identity column in table 'FOO' when IDENTITY_INSERT is set to OFF.
Code example. We have a 1-to-1 class mapping:
public class Foo /* child */
{
public int FooPrimaryKey { get; set; }
public int BarPrimaryKey { get; set; }
public virtual Bar PropertyBar {get; set; }
}
public class Bar
{
public int BarPrimaryKey { get; set; }
public virtual Foo PropertyFoo {get; set; }
}
modelBuilder.Entity<Foo>(entity =>
{
entity.HasKey(e => e.FooPrimaryKey);
entity.ToTable("FOO", "dbo");
entity.HasOne(d => d.PropertyBar)
.WithOne(x => x.PropertyFoo)
// wrong, this throws the above exception
.HasForeignKey<Bar>(x => x.BarPrimaryKey);
});
The foreign key should instead be (same key, different type):
.HasForeignKey<Foo>(x => x.BarPrimaryKey);
If you don't want to use EF core's auto-generating primary key values feature, you can turn it off. You can add your data to the primary key
It should resolve the error - Set Identity Insert off
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int StudentId { get; set; }
Setting Database Generation option to None helped me.
You can find more about it here- https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations
Add this line in order to allow the Id column to receive 1,2,3 and 4 values instead of being auto-numbered.
Sql("SET IDENTITY_INSERT MembershipTypes ON")

Entity Framework to read a column but prevent it being updated

Given a database table with a column that contains historic data but that is no longer populated, is there a way in Entity Framework to read the column but prevent it being updated when using the same model object?
For example I have an object
public class MyObject
{
public string CurrentDataColumnName { get; set; }
public string HistoricDataColumnName { get; set; }
}
From the documentation I don’t believe I can do either of the following, because this will stop EF reading the data as well as persisting it.
(1) Decorate the HistoricDataColumnName property with the following attribute
[NotMapped]
(2) Add the following to my EntityTypeConfiguration for MyObject
Ignore(x => x.HistoricDataColumnName)
You can mark the column as computed to prevent Entity Framework from updating / inserting into that column.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string HistoricDataColumnName { get; set; }
DatabaseGenerated
An important database features is the ability to have computed
properties. If you're mapping your Code First classes to tables that
contain computed columns, you don't want Entity Framework to try to
update those columns. But you do want EF to return those values from
the database after you've inserted or updated data. You can use the
DatabaseGenerated annotation to flag those properties in your class
along with the Computed enum. Other enums are None and Identity.
You can simply use IsModified to check whether a specific entity property was modified or not and by this way you can still Read,Insert and Delete data:
var item = context.MyObjects.Find(id);
item.CurrentDataColumnName = "ChangedCurrentDataColumnName";
item.HistoricDataColumnName = "ChangedHistoricDataColumnName";
context.Entry(item).Property(c => c.HistoricDataColumnName).IsModified = false;
context.SaveChanges();
By using IsModified = false you are excluding the HistoricDataColumnName property from updating, so the HistoricDataColumnName column will not be updated in the database but other properties will be updated.
Setting this value to false for a modified property will revert the change by setting the current value to the original value. If the result is that no properties of the entity are marked as modified, then the entity will be marked as Unchanged. Setting this value to false for properties of Added, Unchanged, or Deleted entities is a no-op.
Check the following answer as a supplementary explanation. It might be helpful also:
https://stackoverflow.com/a/13503683/2946329
Codewise you can set the setter simply to protected. EF useses reflection to materialize your model. I think the now hidden setter also shows to every other programmer, that the field should not be modified any longer.
Also add an [Obsolete]-attribute with further information, why the property can't be set from the public anymore.
Since you say 'at the EF level or lower' a possible solution is to use a trigger to either raise an error if an attempt is made to change the column, or allow the update but ignore the change on the column of interest.
Option 1 - raise an error
CREATE TRIGGER MyTable_UpdateTriggerPreventChange
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
if update(HistoricDataColumnName)
begin
raiserror (50001, 16, 10)
end
END
Option 2 - ignore the change
CREATE TRIGGER MyTable_UpdateTriggerIgnore
ON dbo.Table1
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
update dbo.Table1 set HistoricDataColumnName=inserted.HistoricDataColumnName
from inserted
where inserted.Id = dbo.Table1.Id
END
You could of course do something similar for inserts if required.
Alternatively to raiserror use 'throw'
ALTER TRIGGER MyTable_UpdateTriggerPreventChange
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
if update(HistoricDataColumnName)
begin
throw 50002, 'You can''t change the historic data', 1
end
END
either way you'll get an exception thrown. This is using LinqPad
For just on column this is overkill, but in general you can override SaveChanges in DbContext to have more control on the changes.
In your model:
public override int SaveChanges()
{
var modifiedEntries = base.ChangeTracker.Entries<MyObject>()
.Where(e => e.State == EntityState.Modified).ToList();
foreach (var entry in modifiedEntries)
{
// Overwriting with the same value doesn't count as change.
entry.CurrentValues["HistoricDataColumnName"] = entry.OriginalValues["HistoricDataColumnName"];
}
return base.SaveChanges();
}
But you could also undo all modifications by changing the state from modified to unchanged.
-- UPDATE --
There is one thing that worries me. As soon as a developer has the credentials to access the database you cannot prevent them from doing things you don't want. They could create their own model or query the database directly.
So I think the most important thing to do is to set the field to readonly in the database for the client. But you may not be able to lock one column.
Even if this is not an issue, I think (for design) it is better to move all historical data to other tables. Making it easy to grant readonly access only. You can map these tables 1:1. With Entity Framework you can still access the historical information quite easy.
But in that case you won't have the problem you have now and will give you other options to prevent others from changing the historical information.
internal access modifier
You could change the setter to internal
public class MyObject
{
public string CurrentDataColumnName { get; internal set; }
public string HistoricDataColumnName { get; internal set; }
}
This doesn't impose as much limitations as the other options, but depending on your requirements, this can be quite useful.
protected access modifier
This would probably be the most common usage of making a property in EF "read-only". Which essentially only allows the constructor to access the setter (and other methods within the class, and classes derived from the class).
public class MyObject
{
public string CurrentDataColumnName { get; protected set; }
public string HistoricDataColumnName { get; protected set; }
}
I think protected is what you're looking for.
protected internal access modifier
You can also combine the two like this, to make it protected or internal
public class MyObject
{
public string CurrentDataColumnName { get; protected internal set; }
public string HistoricDataColumnName { get; protected internal set; }
}
Access Modifier Refresher Course
A internal member is accessible only within the same assembly
A protected member is accessible within its class and by derived class instances.
A protected internal member can be accessed from the current assembly or from types that are derived from the containing class.
The question is about EF 6, but this is easily doable in EF Core with the Metadata.IsStoreGeneratedAlways property. Thanks to ajcvickers on the EF Core repo for the answer.
modelBuilder
.Entity<Foo>()
.Property(e => e.Bar)
.ValueGeneratedOnAddOrUpdate()
.Metadata.IsStoreGeneratedAlways = true;
Why do this in EF in the first place? Why not simply ensure that any login being used to access the database either has the rights for performing UPDATE/INSERT/DELETE revoked or even go to the extreme of setting the database to READ_ONLY in the Database options?
It seems to me that any attempt to prevent updates via EF is doomed as you can always circumvent that and, for example, just execute SQL code directly against the EF connection.
As for me, it's simple solution - make property setters as private:
public class MyObject
{
public string CurrentDataColumnName { get; private set; }
public string HistoricDataColumnName { get; private set; }
}
EF will materialize objects from database without any problem, but yout won't have any way to change value int these properties.

Categories

Resources