[英]Force Entity Framework Core to reload some fields after SaveChanges is called
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.我有一个项目使用 Entity Framework Core 在 Postgres 的表中插入/更新记录,并且我遇到了 C# 中某些值的数值精度高于 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.例如,我可能具有数数0.99878
内存在物业类型double
在C#中,但是当我保存这个值,也可能被存储在Posgres为0.9988
,而不是,这是完全正常和预期。
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.实体框架不知道 Postgres 在存储这个数字时做了什么,一切似乎都很好,但实际上我在内存中的数字是不正确的,除非我手动告诉 EF 刷新实体。
I can't increase the precision in the database , but I would like to avoid having to manually refresh entities after every SaveChanges
我无法提高数据库中的精度,但我想避免在每次SaveChanges
后手动刷新实体
How can I tell Entity Framework Core to automatically refresh some fields after SaveChanges
?如何告诉 Entity Framework Core 在SaveChanges
后自动刷新某些字段?
I tried setting .ValueGeneratedOnAddOrUpdate
but that ignores the value that I have in memory.我尝试设置.ValueGeneratedOnAddOrUpdate
但这忽略了我在内存中的值。
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.我在 Entity Framework 开源存储库中询问过,此功能不存在(至少在当前的 .NET 5 版本中今天如此)并已添加到待办事项中以供考虑。
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 https://github.com/dotnet/efcore/issues/23196#issuecomment-723494947
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.