I have a project using Entity Framework Core to insert/update records in a table in Postgres and I have cases where the numeric precision of some values in C# is higher than the numeric precision of Postgres.
For example, I might have the number the number 0.99878
in memory on a property of type double
in C#, but when I save this value it might be stored in Posgres as 0.9988
instead, and that's totally fine and expected.
Entity Framework doesn't know about what Postgres did when storing this number, and everything seems fine, but in reality the number I have in-memory is incorrect unless I manually tell EF to refresh the entity.
I can't increase the precision in the database , but I would like to avoid having to manually refresh entities after every SaveChanges
How can I tell Entity Framework Core to automatically refresh some fields after SaveChanges
?
I tried setting .ValueGeneratedOnAddOrUpdate
but that ignores the value that I have in memory.
Silently losing precision is certainly a problem. For my own use case, involving tracking currency, I found that behaviour to be unacceptable. To keep code repetition to a minimum I introduced a validation attribute, forced every decimal in the model to have it, used it to define the column type in the database, and re-validate every modified object before saving.
[AttributeUsage(AttributeTargets.Property)]
public class PrecisionAttribute : ValidationAttribute
{
public PrecisionAttribute(int precision, int scale)
{
Precision = precision;
Scale = scale;
}
public int Precision { get; set; }
public int Scale { get; set; }
public override bool IsValid(object value)
{
if (value is decimal val)
{
if (val == 0)
return true;
var sqlDec = new SqlDecimal(val);
try
{
return (sqlDec == SqlDecimal.ConvertToPrecScale(sqlDec, Precision, Scale)).Value;
}
catch (Exception)
{
return false;
}
}
var dec = value as decimal?;
return !dec.HasValue || IsValid(dec.Value);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (IsValid(value))
return ValidationResult.Success;
return new ValidationResult($"'{value}' is not a valid {validationContext.DisplayName}", new string[] { validationContext.MemberName });
}
public void Apply(IMutableProperty property) => property.SetColumnType($"numeric({Precision},{Scale})");
}
// ... DbContext ...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
foreach (var table in modelBuilder.Model.GetEntityTypes())
{
foreach (var col in table.GetProperties())
{
var precision = col.PropertyInfo?.GetCustomAttribute<PrecisionAttribute>() ?? col.FieldInfo?.GetCustomAttribute<PrecisionAttribute>();
if (precision != null)
precision.Apply(col);
else if(col.ClrType == typeof(decimal) || col.ClrType == typeof(decimal?))
throw new Exception($"Decimal column {table.ClrType.Name}.{col.Name} doesn't have a precision!");
}
}
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
PreSave();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public override int SaveChanges(bool accept) {
PreSave();
return base.SaveChanges(accept);
}
private void PreSave()
{
if (!ChangeTracker.HasChanges())
return;
var serviceProvider = this.GetService<IServiceProvider>();
var items = new Dictionary<object, object>();
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Added:
case EntityState.Modified:
var context = new ValidationContext(entry.Entity, serviceProvider, items);
Validator.ValidateObject(entry.Entity, context, true);
break;
}
}
}
I asked in the Entity Framework opensource repository and this feature does not exist (at least today with the current .NET 5 release) and was added to the backlog for consideration.
Support store-generated values that are both written to and also read back from the database in the same command
https://github.com/dotnet/efcore/issues/23196#issuecomment-723494947
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.