Closed itsthekeming closed 1 year ago
Exactly the same problem here. Seems like EF Core treats the property that I configured with ValueGeneratedOnAddOrUpdate
as calculated column and tries to read the column value from the DB after insert as if it was configured with HasDefaultValue
/HasComputedColumnSql
.
Just like @ebsndrs, this happens with DateTimeOffset
properties on EF Core 3.1.1 with MSSQL and even the same Windows & VS versions. DateTimeOffset
properties configured with ValueGeneratedOnAdd
behave as expected.
Looks like a duplicate of #6999. Value generators are not called for updates.
See also https://github.com/aspnet/EntityFramework.Docs/issues/491
Thank you for pointing to those other issues @ajcvickers. Are there still no plans to support value generators for updates in the future?
I ended up using the ChangeTracker.Tracked
and ChangeTracker.StateChanged
in my DbContext to get this functionality. I hope the EF Core team considers allowing value generators on update operations in the future; it would be very useful for this sort of thing.
It would be great if this was supported. At the very least, the docs for Generated Values > Value generated on add or update in the current state are somewhat misleading as they claim that:
How the value is generated for added and updated entities will depend on the database provider being used. Database providers may automatically setup value generation for some property types, while others will require you to manually setup how the value is generated.
However reading through it, I believe that's because
ValueGeneratedOnAdd' = '[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
ValueGeneratedOnUpdate' = '[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
(this seems to be also basically what DatabaseGeneratedAttributeConvention
does)
Better docs issue: https://github.com/aspnet/EntityFramework.Docs/issues/1398
Yes, we do plan to make improvements in this area, but closing this issue as duplicate since the functionality is primarily tracked by #6999.
Is there a way we can extend the functionality until it's available natively? I think it's a good feature to add and valid use case.
Continue to configure the generator with your Fluent API:
builder.Property(x => x.LastModified)
.HasValueGenerator<UtcDateTimeGenerator>()
.ValueGeneratedOnAddOrUpdate();
Update your DbContext
to add the following methods:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
GenerateOnUpdate();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
GenerateOnUpdate();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void GenerateOnUpdate()
{
foreach (EntityEntry entityEntry in ChangeTracker.Entries())
{
foreach (PropertyEntry propertyEntry in entityEntry.Properties)
{
IProperty property = propertyEntry.Metadata;
Func<IProperty, IEntityType, ValueGenerator> valueGeneratorFactory =
property.GetValueGeneratorFactory();
bool generatedOnUpdate = (property.ValueGenerated & ValueGenerated.OnUpdate)
== ValueGenerated.OnUpdate;
if (!generatedOnUpdate || valueGeneratorFactory == null)
{
continue;
}
ValueGenerator valueGenerator = valueGeneratorFactory.Invoke(
property,
entityEntry.Metadata);
propertyEntry.CurrentValue = valueGenerator.Next(entityEntry);
}
}
}
I have the problem on Oracle 18C - I need to fill PK in the table, PK is a NUMBER, not IDENTITY (this is obviously a defect and will be changed later on, but now I have to deal with that since I don't have rights to change DB structure, however I need to prepare CRUD demo). I don't want to use some C# value generator, but instead - DB remedy. So I tried to use the following (but it did not work - the expression is ignored, "ORA-01400: cannot insert NULL"):
b.HasKey(x => x.Id); b.Property(x => x.Id).HasColumnName("C_LICENCE").IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("select round(dbms_random.value(100000, 999999)) from dual");
I suspect it's probably because int primary column is never null :) But anyway, I need to somehow force it to be generated via SQL always.
@alexandis it seems like you want your database to generate the values, but can't change your database schema to do that? If you can't make your database generate values, you must do that on the client. Either you manually do this in application code, or you can set up a value generator in EF Core - which you also seem to not want... Can you be more specific on exactly what you're looking for?
@roji I think I don't have rights to change the table schema, however I'm free to insert values into DB in a way I want. So what I would like to achieve is - having 'incorrect' oracle table structure, where PK ID is defined, but it's a NUMBER instead of IDENTITY - to insert a new record like: INSERT INTO MYTABLE (ID) VALUES ((select round(dbms_random.value(100000,999999)) from dual). How am I supposed to do this?
@alexandis if you can't change your database column definition - to define a default expression or configure it as identity - then it's up to your program to provide those values. The easiest is probably to set a value generator with EF Core, is there any reason you're trying to avoid that?
PS using random number generation like the above is a bad idea - you'll have collisions and I'm assuming ID needs to be unique.
@roji I do not worry about duplicates, since for demo purposes I will need to insert probably just dozens of entries (the client will change the architecture later on). On a second thought - you are right, why not to use .NET "randomize" mechanism, it will be more or less the same. The only reason I might try to avoid that - is to make DB check if the next generated ID is unique for the table. But for this, I would need to create a SEQUENCE in the DB (and again - I have no right for that and all in all, it makes no sense either) :) So I think I am giving up with HasDefaultValueSql now - the only question remains, why it did not work in my case anyway? :)
@alexandis in order to understand why HasDefaultValueSql didn't work for you, we'd need to see a runnable code sample. If you're interested in pursuing that, please open a new issue issue with the proper code.
I'm having this issue too. The documentation and IntelliSense on the ValueGeneratedOnAddOrUpdate()
method is very misleading. At the very least I would expect it to use ValueGenerators on update the same way that ValueGeneratedOnAdd
does.
Having ValueGenerators work on update would also allow solutions built with EF Core be much more DBMS agnostic since using C# ValueGenerators means you're not relying on DBMS specific features/SQL snippets and/or triggers to achieve the result. This would allow developers of applications to support multiple DBMSs more easily.
@eegeeZA I've added your workaround to my solution and, although I can step through and see it setting the "LastModified" DateTime property on my entity, the SQL generated by ef core omits the column in question resulting in a SQL Exception cannot insert nulls error.
In my case, the "LastModified" property is inherited from an abstract base class (an AuditedAggregateRoot class that just adds Created
and LastModified
properties) - if that has any bearing - and I'm running in .Net 5 with EF Core 5.0.2.
@hades200082 there's some relevant information on this in this page of the EF docs.
tl;dr different database support very different mechanisms for value generation, both for add and for update. In some restricted cases, EF providers will indeed set up value generation automatically (e.g. integer key properties are automatically set up as identity). But for anything beyond that minimum, you need to specifically set up value generation yourself - e.g. a trigger - since there's no way for EF Core to know what you want.
@roji There is a way for EF Core to know what I want... Me telling it what I want through the type configuration.
I've told it when I want it to generate a value by using .ValueGeneratedOnAddOrUpdate()
.
I've told it how to generate that new value by using .HasValueGenerator<UtcDateTimeGenerator>()
So, I've told it both when I want it to generate a value for the property and exactly how to generate that value in C# code. No special database functionality required.
Why doesn't this work for updates as the docs and IntelliSense suggests it should?
From PropertyBuilder.cs:
/// <summary>
/// Configures a property to have a value generated when saving a new or existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> ValueGeneratedOnAddOrUpdate()
=> (PropertyBuilder<TProperty>)base.ValueGeneratedOnAddOrUpdate();
/// <summary>
/// Configures a property to have a value generated when saving an existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> ValueGeneratedOnUpdate()
=> (PropertyBuilder<TProperty>)base.ValueGeneratedOnUpdate();
I understand that it's not always possible to have the database do the generation automatically, but there should be no reason that EF can't generate the value based on providing the when and the how in the type configuration.
In addition, using the workaround posted above by @eegeeZA I can see that the ValueGenerator is being called prior to saving changes and yet it's still not including the property in the SQL that's generated for an insert and still doesn't update the column for updates when properties are configured with .ValueGeneratedOnAddOrUpdate()
.
As written above in https://github.com/dotnet/efcore/issues/19765#issuecomment-581526253, Value generators are not called for updates, only for inserts (#6999 tracks that).
You're right that our docs aren't sufficient for client-side value generation, I've opened https://github.com/dotnet/EntityFramework.Docs/issues/3057 to track that.
You are correct at the moment, but surely given the fact that we can specify that we want values generated on update, and there is already a mechanism in place that could do this, it would make sense to do it?
It also doesn't explain why setting a value in overridden SaveChanges still throws a SQL null exception.
I'm not saying it doesn't make sense - there's an issue tracking that in the backlog. We haven't done it because there are many other high-priority work items, and that issue has hardly received any user interest/votes.
For the null exception, can you please open a new issue with a runnable code sample so we can investigate?
For clarification, it is important to set GeneratesTemporaryValues
to false
if the DB does not replace the null
with a value. @hades200082
/// <summary>
/// Generates <see cref="DateTime" /> values using <see cref="DateTime.UtcNow" />.
/// The generated values are non-temporary, meaning they will be saved to the database.
/// </summary>
public class UtcDateTimeGenerator : ValueGenerator<DateTime>
{
/// <summary>
/// Gets a value indicating whether the values generated are temporary or permanent. This implementation
/// always returns false, meaning the generated values will be saved to the database.
/// </summary>
public override bool GeneratesTemporaryValues => false;
/// <summary>
/// Gets a value to be assigned to a property.
/// </summary>
/// <param name="entry">
/// The change tracking entry of the entity for which the value is being generated.
/// </param>
/// <returns>
/// The value to be assigned to a property.
/// </returns>
public override DateTime Next(EntityEntry entry) => DateTime.UtcNow;
}
Yep @eegeeZA.
Here's mine -
public class UtcDateTimeValueGenerator : ValueGenerator<DateTime>
{
public override DateTime Next(EntityEntry entry) => DateTime.UtcNow;
public override ValueTask<DateTime> NextAsync(EntityEntry entry,
CancellationToken cancellationToken = new CancellationToken()) =>
ValueTask.FromResult<DateTime>(DateTime.UtcNow);
public override bool GeneratesTemporaryValues => false;
}
In your GenerateOnUpdate
if I comment out your code and just do...
if (entityEntry.Properties.Any(x => x.Metadata.Name == "LastModified"))
{
entityEntry.Properties.Single(x => x.Metadata.Name == "LastModified").CurrentValue = DateTime.UtcNow;
}
... everything works, but I'd rather have it driven by the ValueGenerator.
Now that is weird. The code is doing the same kind of action, but through a more direct-hardcoded way. Try adding the inside of the if
to the last line of the workaround. Then check in the debugger if
propertyEntry == entityEntry.Properties.Single(x => x.Metadata.Name == "LastModified")
valueGenerator.Next(entityEntry) == DateTime.UtcNow
(approximiately)
and let it run through. Based on what you said, it should do the correct action after the ValueGenerator. Hopefully, the debugger reveals why.This doesn't work (note the last line):
private void GenerateOnUpdate()
{
foreach (var entityEntry in ChangeTracker.Entries())
{
foreach (var propertyEntry in entityEntry.Properties)
{
var property = propertyEntry.Metadata;
var valueGeneratorFactory =
property.GetValueGeneratorFactory();
var generatedOnUpdate = (property.ValueGenerated & ValueGenerated.OnUpdate)
== ValueGenerated.OnUpdate;
if (!generatedOnUpdate || valueGeneratorFactory == null)
{
continue;
}
var valueGenerator = valueGeneratorFactory.Invoke(
property,
entityEntry.Metadata);
propertyEntry.CurrentValue = valueGenerator.Next(entityEntry);
entityEntry.Properties.Single(x => x.Metadata.Name == "LastModified").CurrentValue = DateTime.UtcNow;
}
}
}
I verified that the last line of code inside the loops is executed but it still throws a SQL Exception because it's trying to insert a NULL in the LastModified column.
The following code works fine, without throwing the exception, though:
private void GenerateOnUpdate()
{
foreach (var entityEntry in ChangeTracker.Entries())
{
if (entityEntry.Properties.Any(x => x.Metadata.Name == "LastModified"))
{
entityEntry.Properties.Single(x => x.Metadata.Name == "LastModified").CurrentValue = DateTime.UtcNow;
}
}
}
Something is not adding up. I would have expected the code with the extra (temporarily hard-coded) line would have worked. One of the statements leading up to the extra line might have a side effect that is interfering. The code was working at the time of writing. A suggestion is to try commenting out lines from the bottom up to find the culprit.
You're right - something isn't adding up here. I now have this and am still getting the exception:
private void GenerateOnUpdate()
{
foreach (var entityEntry in ChangeTracker.Entries())
{
foreach (var propertyEntry in entityEntry.Properties)
{
if (propertyEntry.Metadata.Name == "LastModified")
{
propertyEntry.CurrentValue = DateTime.UtcNow;
}
}
}
}
This also fails:
private void GenerateOnUpdate()
{
foreach (var entityEntry in ChangeTracker.Entries())
{
if (entityEntry.Properties.Any(x => x.Metadata.Name == "LastModified"))
{
entityEntry.Property("LastModified").CurrentValue = DateTime.UtcNow;
}
}
}
And now, even the previously working examples don't work. I'm so confused.
In addition, when I put a break-point on the start of the GenerateOnUpdate()
method and inspect the entity properties for each entity in the change tracker (only one at this point) the LastModified property has a legitimate value in both CurrentValue and OriginalValue - both the same... yet I've not set them yet and it still throws the null SQL exception.
I feel like I'm going insane.
You will need to start searching in other places. Verify the mapping configuration/annotations, check the value of PropertyEntry.IsTemporary, check the result of DbEntityValidationException.EntityValidationErrors, and possibly add EF Core logging.
Here is my workaround
Continue to configure the generator with your Fluent API:
builder.Property(x => x.LastModified) .HasValueGenerator<UtcDateTimeGenerator>() .ValueGeneratedOnAddOrUpdate();
Update your
DbContext
to add the following methods:public override int SaveChanges(bool acceptAllChangesOnSuccess) { GenerateOnUpdate(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override Task<int> SaveChangesAsync( bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { GenerateOnUpdate(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void GenerateOnUpdate() { foreach (EntityEntry entityEntry in ChangeTracker.Entries()) { foreach (PropertyEntry propertyEntry in entityEntry.Properties) { IProperty property = propertyEntry.Metadata; Func<IProperty, IEntityType, ValueGenerator> valueGeneratorFactory = property.GetValueGeneratorFactory(); bool generatedOnUpdate = (property.ValueGenerated & ValueGenerated.OnUpdate) == ValueGenerated.OnUpdate; if (!generatedOnUpdate || valueGeneratorFactory == null) { continue; } ValueGenerator valueGenerator = valueGeneratorFactory.Invoke( property, entityEntry.Metadata); propertyEntry.CurrentValue = valueGenerator.Next(entityEntry); } } }
@eegeeZA your workaround is really helpful but it doesn't work. Somewhere, I have this update code...
public async Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken)
{
_dbContext.Set<TEntity>().Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
return await _dbContext.SaveChangesAsync(cancellationToken);
}
But column like rowguid that is not present as a property in my entity/model class but present in the entity's configuration as...
entity.Property(e => e.Rowguid).HasColumnName("rowguid")
.HasValueGenerator<RowGuidValueGenerator>().ValueGeneratedOnAddOrUpdate();
...and uses a value generator as seen above to generate GUID does not get updated with newly generated GUID even though your code at...
propertyEntry.CurrentValue = valueGenerator.Next(entityEntry);
...actually generates a new GUID. It didn't get saved to the database.
Kindly help.
@eegeeZA BTW, the code above is about scaffolded DbContext, just in case it has anything to do with it.
The code for GenerateOnUpdate()
will be called before saving, but still goes through the normal EF save method. Try setting the value manually without using a value generator and check if it saves. If the column's value is generated on the DB, setting it on the C# side will not have an effect.
Hi, if you still haven't found a solution, you can try adding .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save)
in the property config along with the suggested workaround.
overriding-value-generation
ou are correct at the moment, but surely given the fact that we can specify that we want values generated on update, and there is already a mechanism in place that could do this, it would make sense to do it?
It also doesn't explain why setting a value in overridden SaveChanges still throws a SQL null exception.
@roji There is a way for EF Core to know what I want... Me telling it what I want through the type configuration.
I've told it when I want it to generate a value by using
.ValueGeneratedOnAddOrUpdate()
.I've told it how to generate that new value by using
.HasValueGenerator<UtcDateTimeGenerator>()
So, I've told it both when I want it to generate a value for the property and exactly how to generate that value in C# code. No special database functionality required.
Why doesn't this work for updates as the docs and IntelliSense suggests it should?
From PropertyBuilder.cs:
/// <summary> /// Configures a property to have a value generated when saving a new or existing entity. /// </summary> /// <returns> The same builder instance so that multiple configuration calls can be chained. </returns> public new virtual PropertyBuilder<TProperty> ValueGeneratedOnAddOrUpdate() => (PropertyBuilder<TProperty>)base.ValueGeneratedOnAddOrUpdate(); /// <summary> /// Configures a property to have a value generated when saving an existing entity. /// </summary> /// <returns> The same builder instance so that multiple configuration calls can be chained. </returns> public new virtual PropertyBuilder<TProperty> ValueGeneratedOnUpdate() => (PropertyBuilder<TProperty>)base.ValueGeneratedOnUpdate();
I understand that it's not always possible to have the database do the generation automatically, but there should be no reason that EF can't generate the value based on providing the when and the how in the type configuration.
In addition, using the workaround posted above by @eegeeZA I can see that the ValueGenerator is being called prior to saving changes and yet it's still not including the property in the SQL that's generated for an insert and still doesn't update the column for updates when properties are configured with
.ValueGeneratedOnAddOrUpdate()
.
Amen, this is totally idiotic.
Seems to me that having the .HasValueGenerator() set on the property at all will cause it to not update the value. So regardless of what code you add to update the value in an overridden Save method it will not send it to the database.
Because I like the cleanliness of FluentAPI or Attributes....I threw this together, which allows you to decorate your properties and have the update happen. I haven't tested the performance but I tried to limit the reflection.
Attribute which has three flags, update on insert, update on update, and which timestamp to use.
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class UpdateTimestampAttribute : Attribute
{
private readonly bool _onInsert;
private readonly bool _onUpdate;
private readonly bool _useUtc;
public UpdateTimestampAttribute(bool onInsert, bool onUpdate, bool useUtc)
{
_onInsert = onInsert;
_onUpdate = onUpdate;
_useUtc = useUtc;
}
public bool OnInsert
{
get { return _onInsert; }
}
public bool OnUpdate
{
get { return _onUpdate; }
}
public bool UseUTC
{
get { return _useUtc; }
}
}
Decoration Example
[Column("DATE_ADDED", TypeName = "datetime")]
[UpdateTimestamp(true,false,false)]
public DateTime DateAdded { get; set; }
[Column("DATE_MODIFIED", TypeName = "datetime")]
[UpdateTimestamp(true, true, false)]
public DateTime DateModified { get; set; }
Override your saves with:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnSave();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
OnSave();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnSave()
{
foreach (EntityEntry entityEntry in ChangeTracker.Entries())
{
if (entityEntry.State == EntityState.Modified || entityEntry.State == EntityState.Added)
{
foreach (PropertyEntry propertyEntry in entityEntry.Properties)
{
UpdateTimestampAttribute? uts = propertyEntry.Metadata.PropertyInfo?.GetCustomAttribute<UpdateTimestampAttribute>();
if (uts != null)
{
if ((uts.OnUpdate == true && entityEntry.State == EntityState.Modified) ||
(uts.OnInsert == true && entityEntry.State == EntityState.Added))
{
try
{
if (uts.UseUTC == true)
propertyEntry.CurrentValue = DateTime.UtcNow;
else
propertyEntry.CurrentValue = DateTime.Now;
}
catch (Exception ex)
{
throw new Exception("You can only decorate DateTime objects with UpdateTimestampAttribute", ex);
}
}
}
}
}
}
}
This workaround seems to work well:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
GenerateOnUpdate();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
GenerateOnUpdate();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void GenerateOnUpdate()
{
// Update LastUpdatedAt only if there are changes
foreach (var entityEntry in ChangeTracker.Entries<IEntity>().Where(e => e.State == EntityState.Modified))
{
entityEntry.Property(nameof(IEntity.LastUpdatedAt)).CurrentValue = DateTimeOffset.UtcNow;
entityEntry.Property(nameof(IEntity.LastUpdatedAt)).IsModified = true;
}
}
In my case, I know that IEntity
has LastUpdatedAt
, but this works as well:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
GenerateOnUpdate();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
GenerateOnUpdate();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void GenerateOnUpdate()
{
foreach (var entityEntry in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
{
if (entityEntry.Properties.Any(x => x.Metadata.Name == "LastUpdatedAt"))
{
entityEntry.Property("LastUpdatedAt").CurrentValue = DateTimeOffset.UtcNow;
entityEntry.Property("LastUpdatedAt").IsModified = true;
}
}
}
Note that I'm using DateTimeOffset
. If LastUpdatedAt
is DateTime
, convert to DateTime.UtcNow
.
This is combined with the following in protected override void OnModelCreating(ModelBuilder modelBuilder)
:
entity.Property<DateTimeOffset>(nameof(IEntity.LastUpdatedAt))
.HasDefaultValueSql("now()")
.ValueGeneratedOnAdd()
.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Save);
now()
is for Postgres. My guess is that I can omit it from here, unless I'm communicating with the DB also not via EF Core, and in that case having a trigger is a better option.
I'm having trouble getting
ValueGeneratedOnUpdate
andValueGeneratedOnAddOrUpdate()
to work for aDateTimeOffset
property in my model. When I callAddAsync()
orUpdate()
, the entity local reflects the newly generated values. However, callingSaveChangesAsync()
results in either an exception or no change to the values in question.ValueGeneratedOnAdd()
appears to be working properly.Steps to reproduce
My model has two DateTimeOffset properties.
I'm using the fluent API to configure my context.
And I have a value generator (the
GuidValueGenerator
is similar, but returnsGuid.NewGuid()
instead).Finally, I have my controller actions.
If:
CreatedDateTime
usesValueGeneratedOnAdd()
LastModifiedDateTime
usesValueGeneratedOnAddOrUpdate()
HasValueGenerator(typeof(DateTimeOffsetValueGenerator))
Then the following occurs: After
dbContext.AddAsync(account)
completes, I can see that bothCreatedDateTime
andLastModifiedDateTime
have the correct values in my locals.LastModifiedDateTime
is notnull
. However,dbContext.SaveChangesAsync()
throws aDbUpdateException
, claiming it isnull
. Here is the stack trace (I've changed some names/paths to protect info):Okay, so
ValueGeneratedOnAddOrUpdate()
throws an exception. I tried changing the configuration to useValueGeneratedOnUpdate()
forLastModifiedDateTime
instead. This results in the correct value being generated and saved when I create the entity, but when I try to update the entity,LastModifiedDateTime
still has the old value. No new value is generated and saved. Any properties I manually update do successfully save.I did try calling both
ValueGeneratedOnAdd()
andValueGeneratedOnUpdate()
on the configuration ofLastModifiedDateTime
and it behaved the same as if onlyValueGeneratedOnUpdate()
was present.I did find issue #3955, but I'd prefer to generate the value in code instead of passing in a SQL function.
The
CreatedDateTime
property has no trouble generating the value, so I believe the value generation is being implemented properly. Am I missing something?Further technical details
EF Core version: 3.1.1 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET Core 3.1 Operating system: Windows 10 Version 1903 Build 18362.592) IDE: Visual Studio 2019 16.4.3