Is there a way to mark a property as unchangeable on updates? - c#

I really want to avoid accidental overwrites on a specific property. ValueGenerated.OnAdd is not useful in this regard because it still tries to set a value in the database (1), unfortunately. I want to essentially make this field read-only so that updates never attempt to overwrite the value that is there. It's an audit-based field that I only need returned upon creation and via select statements.
(1) From EF: (https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.valuegenerated?view=efcore-7.0)
EF may still attempt to save a specific value (rather than having one generated by the database)

Specify the Save Behavior as either "PropertySaveBehavior.Ignore" or "PropertySaveBehavior.Throw" in your modelbuilder depending on which behavior you want:
modelBuilder
.Entity<YourEntity>()
.Property(e => e.YourProperty)
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
This will allow an initial insert, but no updates to the field afterwards.
See: https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.mutablepropertyextensions.setaftersavebehavior?view=efcore-5.0

Related

EF Core - data may have been modified or deleted since entities were loaded with SQL ON UPDATE CURRENT_TIMESTAMP

I'm getting "data may have been modified or deleted since entities were loaded" exception while trying to update the same record for the second time. I found the issue is because I'm using the below column definition in MySQL and it seems I'm not handling this correctly in my entities.
last_updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
dbItem.LastUpdatedAt = DateTime.Now;
If I update the value from my entity as above and save then it works.
But if I don't update the value from my entity it doesn't work for the second update throwing "data may have been modified or deleted since entities were loaded" exception
What would be a good solution for this.
What you want is, that EF will get the updated value back from database at the end of an add/update operation. You can use ValueGeneratedOnAddOrUpdate configuration to achieve this.
Contrary to what the name suggests, you are still allowed to provide your own value that will be included in the context - just make sure that you don't try to provide the C# default value if you want to transfer the value to the database
If you set a value for the property configured as ValueGeneratedOnAddOrUpdate while the entity is being tracked by the context, the property and the value that you set will be included in any INSERT and UPDATE statements. This value may be saved in the database, depending on how you have configured your value generation strategy. This is only applicable if the value that you provide is not the CLR default value for the data type of the property.
Quoted from https://www.learnentityframeworkcore.com/configuration/fluent-api/valuegeneratedonaddorupdate-method
Disclaimer: I never tried this practically. It's just what I assume to work after reading some docs.

How to not specify SQL expression in HasComputedColumnSql of EF Core Fluent API in DB first approach?

In previous version of EF, to specify a computed column we would write:
modelBuilder
.Entity<Type>()
.Property(x => x.ComuptedProperty)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
This would make sense, because the SQL expression for the computed column is written once in database.
However, after migrating to EF Core, we realized that the syntax should be changed into:
modelBuilder
.Entity<Type>()
.Property(x => x.ComuptedProperty)
.HasComputedColumnSql("SQL Expression should be duplicated here");
This makes sense when we go code first. Because EF Core uses this SQL expression while creating the table.
However, for DB first scenarios this doesn't make sense at all. We tried to leave this parameter empty, and it throws an exception complaining:
The string argument 'sql' cannot be empty
Now things get even worse when you want to have a data access generator. How can we neglect this parameter?
Indeed when using HasComputedColumnSql you must specfiy the SQL query that will be used for the computed column when generating SQL Script for the associated table. Like you say, this is useful only for Code First approach.
In Database First approach, you can use one of the following methods frol PropertyBuilder<TProperty> type (description are from XML documentaiton of those methods):
ValueGeneratedOnAdd(): Configures a property to have a value generated only when saving a new entity,unless a non-null, non-temporary value has been set, in which case the set value will be saved instead. The value may be generated by a client-side value generator or may be generated by the database as part of saving the entity.
ValueGeneratedOnAddOrUpdate(): Configures a property to have a value generated when saving a new or existing entity.
ValueGeneratedOnUpdate(): Configures a property to have a value generated when saving an existing entity.
In your case because it's a computed column then the value maybe generated when adding and saving the data so you must use ValueGeneratedOnAddOrUpdate() method. Again EF documentation say that :
This just lets EF know that values are generated for added or updated entities, it does not guarantee that EF will setup the actual mechanism to generate values.

Entity Framework concurrency issue using stored procedures

I am using ASP.NET to build a application for a retail company. I am using the Entity Framework (model-first) as my data access layer. I am using stored procedures to do my CRUD operations and all columns are mapped and seems to be correct as all CRUD functionality are working as expected.
But I am having concurrency issues with the DELETE operation.
I've added a TimeStamp column to the table I am doing the CRUD operation on. The UPDATE operation works fine as it is updating by primary key and the TimeStamp value. Thus if no rows are affected with the UPDATE operation, because of a change in the TimeStamp value, the Entity Framework throws a OptimisticConcurrencyException.
The DELETE operation works on the same principle as it is deleting by primary key and the TimeStamp value. But no exception is thrown when the TimeStamp value does not match between the entity and the database.
In the C# delete method I do retrieve the latest record first and then update the TimeStamp property to another TimeStamp value (It might be different to the retrieved value). After some investigation by using SQL Profiler I can see that the DELETE stored procedure is executed but the TimeStamp parameter that is passed to the stored procedure is the latest TimeStamp value and not the value that I have set the TimeStamp property to. Thus the record is deleted and the Entity Framework does not throw an exception.
Why would the Entity Framework still pass the retrieved TimeStamp value to the Stored Procedure and not the value that I have assigned the property? Is this be design or am I missing something?
Any help will be appreciated! (where is Julie Lerman when you need her! :-))
Optimistic concurrency in EF works fine. Even with stored procedures.
ObjectContext.DeleteObjects passes original values of entity to delete function. This makes sense. Original values are used to identify the row to delete. When you delete object, you don't (usually) have meaningful edits to your entity. What do you expect EF to do with then? Write? To what records?
One legitimate use for passing modified data to delete function is when you want to track deletes in some other table and you need to throw in some information not accessible at database layer, only at business layer. Examples include application level user name or reason to delete. In this situation you need to construct entity with this values as original values. One way to do it:
var x = db.MyTable.Single(k => k.Id == id_to_delete);
x.UserName = logged_in_user;
x.ReasonForChange = some_reason;
// [...]
db.ObjectStateManager.ChangeObjectState(x, EntityState.Unchanged);
db.MyTable.DeleteObject(x);
db.SaveChanges();
Of course, better strategy might be to do it openly in business layer.
I don't understand your use case with rowversion/timestamp.
To avoid concurrency issues you pass original timestamp to modifying code.
That way it can be compared to current value in database to detect if record changed since you last read it.
Comparing it with new value makes little sense.
You usually use change markers that are automatically updated by database like rowversion/timestamp in SQL Server, rowversion in Oracle or xmin in PostgreSQL.
You don't change its value in your code.
Still, if you maintain row version manually, you need to provide:
a) new version to insert and update to be written, and
b) old version (read from database) to update and delete to check for concurrent changes.
You don't send new value to delete. You don't need to.
Also, when using stored procedures for modification, it's better to compute new version in the procedure and return it to application, not the other way around.
Hard to tell without seeing any code, but maybe when the postback occurs the page is being re-bound before your delete method is firing? On whatever method databinds the form controls (I assume it's OnLoad or OnInit), have you wrapped any databinding calls with if ( !this.IsPostBack ) { ... }?
Also I'm not sure if there's a reason why you're explicitly storing the concurrency flag in viewstate/session variables, but a better way to do it (imo) is to add the timestamp to the DataKeyNames property of the FormView/GridView (ie: <asp:FormView ID='blah' runat='server' DataKeyNames='Id, Timestamp'>.
This way you don't have to worry about manually storing or updating the timestamp. ;)

Fluent NHibernate Generated AND Assigned ID Columns

I'm using Fluent NHibernate for my data-persistence in a web application.
My problem... I have a base class that maps all entities with an ID property of type T (almost always an int or GUID) using GeneratedBy().Identity()
On application start-up, I have a boot-strapper that checks and verifies the needed seed-data is populated. My problem is, some of the seed-data that is populated needs a specific ID. (IDs that would correspond to an enum or system user)
Is there any way to force NHibernate to commit the record using the ID that I specify, rather than an auto-generated one? Any other commits to the repository thereafter can be auto-generated.
Id(x => x.Id).GeneratedBy.Assigned();
If you want the application to assign identifiers (as opposed to having NHibernate generate them), you may use the assigned generator. This special generator will use the identifier value already assigned to the object's identifier property. Be very careful when using this feature to assign keys with business meaning (almost always a terrible design decision).
Due to its inherent nature, entities that use this generator cannot be saved via the ISession's SaveOrUpdate() method. Instead you have to explicitly specify to NHibernate if the object should be saved or updated by calling either the Save() or Update() method of the ISession.
http://nhibernate.info/doc/nhibernate-reference/mapping.html#mapping-declaration-id-assigned
you can use
Id(I => I.Id).GeneratedBy.Increment(); //Increment by 1

LinqToSQL and auditing changed fields

Here's another one of these LinqToSQL questions where I'm sure I must have missed the boat somewhere, because the behavior of the O/R Designer is very puzzling to me...
I have a base class for my LinqToSQL tables, which I called LinqedTable. I've successfully used reflection to get hold of all the properties of the descendant classes and do other standard stuff.
Now I want to have some automatic auditing of my tables, so that whenever a LinqedTable record is inserted or deleted, or a field value changes, I will insert a record into an audit table, detailing the change type, the field name, and its value pre- and post-save.
I thought I would be able to do it using the PropertyChanging event, keeping track of all the changed properties before a save, then clearing the collection of changes after each SubmitChanges() call. But - the generated code from the O/R designer, for some bizarre reason, doesn't give you the property name in the PropertyChanging event - it sends an empty string! (WHY?!) It does send the property name in the PropertyChanged event, but that's already too late for me to get the original value.
I thought to grab all the original values of all properties using the OnLoaded() partial method - but that is private by definition, and I need access to that method in the base class. Even if I used reflection to get hold of that method, that would mean I would have to implement the other half of the partial method for every one of my tables, which kinda defeats the purpose of having inheritance!
I also can't find any suitable method in the DataContext to use or override.
So what would you recommend to get this audit functionality working?
You can use GetChangeSet on the DataContext to retrieve a list of updates, inserts and deletes that have occurred on all tables within a context. You can use ITable.GetOriginalEntityState to retrieve the original values of a changed entity. However, when you retrieve the original values of a deleted or updated record, the associations will not be available so you will have to rely on foreign key values only in that area if you need to process related entities. You can Use ITable.GetModifiedMembers to help retrieve only values that have changed.
Forgive me for perhaps a stupid answer, but how about doing the audit directly in the SQL Server using triggers (if you are in SQL Server 2005 or 2008 standard) or using the change tracking facilities in SQL server 2008 Enterprise?

Categories

Resources