其他系列博文
背景
在实际开发场景中,我们经常会遇到更新部分数据字段的需要。然而在Abp的默认关系型数据库ORM(即EF Core)中,并没有提供对数据字段按需更新的实现。本博文将在尽量保证通用性的前提下实现一个简单的EF Core按需更新机制。
目标
支持通过给实体标注特性的方式,引导EF Core更新部分字段。本博文将实现一个更新非默认值字段的机制。
思路
在调用DbContext
的SaveChanges
方法进行数据更新时,EF Core的更改跟踪机制会遍历实体字段获取更改状态信息。通过重写AbpDbContext
的ApplyAbpConceptsForModifiedEntity
方法,我们可以获得一个在进行数据库交互前修改字段更改状态的横切点。另一方面,通过利用PropertyEntry
的Metadata
属性,我们可以传入字段的自定义元数据,以引导横切点的判断逻辑。
因此,大致步骤如下:
- 创建一个标注特性类
- 针对新创建的特性类,封装EF Core的
EntityTypeBuilder
扩展方法,为具有该特性的实体字段添加元数据
- 重写
AbpDbContext
的ApplyAbpConceptsForModifiedEntity
方法,根据实体字段元数据更新字段更改状态
编码
创建特性类PreventUpdateIfDefaultAttribute
,用于标注某字段在默认值时不进行更新:
1 2 3 4
| public class PreventUpdateIfDefaultAttribute : Attribute {
}
|
在扩展库的EntityTypeBuilderExtensions
中,添加一对ConfigurePreventUpdate
与TryConfigurePreventUpdate
方法。如果还没有这个扩展方法类,就在适当位置创建一个,后续还会用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void ConfigureByMyConvention(this EntityTypeBuilder b) { b.TryConfigurePreventUpdate(); }
public static void ConfigurePreventUpdate<T>(this EntityTypeBuilder<T> b) where T : class { b.As<EntityTypeBuilder>().TryConfigurePreventUpdate(); }
public static void TryConfigurePreventUpdate(this EntityTypeBuilder b) { var properties = b.Metadata.GetProperties(); foreach (var property in properties) { if (property.PropertyInfo?.IsDefined(typeof(PreventUpdateIfDefaultAttribute), true) == true) { b.Property(property.Name).PreventUpdateIfDefault(); } } }
|
在上述的TryConfigurePreventUpdate
方法中,博主对每个标注了PreventUpdateIfDefaultAttribute
特性的字段调用了一个PreventUpdateIfDefault
扩展方法。这个扩展方法封装了添加元数据的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public static class MyPropertyBuilderExtensions { public const string PreventUpdateConditionAnnotation = "__PreventUpdateCondition";
public static PropertyBuilder<TProperty> PreventUpdate<TProperty>( this PropertyBuilder<TProperty> propertyBuilder, Func<object, bool> predicate, bool prevent = true) { return propertyBuilder.HasAnnotation(PreventUpdateConditionAnnotation, prevent ? predicate : null); }
public static PropertyBuilder PreventUpdate( this PropertyBuilder propertyBuilder, Func<object, bool> predicate, bool prevent = true) { return propertyBuilder.HasAnnotation(PreventUpdateConditionAnnotation, prevent ? predicate : null); }
public static PropertyBuilder<TProperty> PreventUpdateIfDefault<TProperty>(this PropertyBuilder<TProperty> propertyBuilder) { var defaultValue = TypeHelper.GetDefaultValue<TProperty>(); return propertyBuilder.PreventUpdate(property => property == null || property.Equals(defaultValue)); }
public static PropertyBuilder PreventUpdateIfDefault(this PropertyBuilder propertyBuilder) { var defaultValue = TypeHelper.GetDefaultValue(propertyBuilder.Metadata.ClrType); return propertyBuilder.PreventUpdate(property => property == null || property.Equals(defaultValue)); } }
|
具体而言,对于需要按需更新的字段,博主在PropertyEntry
的元数据中加入了一个Func<object, bool>
类型的参数,用于在运行时判断是否需要更新。
最后,在我们的DbContext
中对Abp基类的方法进行重写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| protected override void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, EntityChangeReport changeReport) { base.ApplyAbpConceptsForModifiedEntity(entry, changeReport);
ApplyPartiallyUpdateConcept(entry); }
protected virtual void ApplyPartiallyUpdateConcept(EntityEntry entry) { ApplyPartiallyUpdateConceptForProperties(entry.Properties); }
private void ApplyPartiallyUpdateConceptForProperties(IEnumerable<PropertyEntry> properties) { if (properties == null) { return; }
foreach (var property in properties) { if (property.Metadata[MyPropertyBuilderExtensions.PreventUpdateConditionAnnotation] is Func<object, bool> predicate) { if (predicate(property.CurrentValue)) { property.IsModified = false; } } } }
|
这样一来,在通过EF Core进行数据库更新操作时,程序会执行我们的横切点逻辑,并对每个字段进行默认值检查,只有非默认值的标有特性字段才会执行更新操作。
实际情况中,可能会有其他不同的按需更新逻辑,但是可通过类似的思路进行实现。
至此,基于Abp的EF Core实体字段按需更新功能已开发完成。在下一篇博文中,博主会对本文实现的功能编写测试。