0%

基于Abp的扩展模块实现第2例 - 数据字段按需更新(开发)

其他系列博文

背景

在实际开发场景中,我们经常会遇到更新部分数据字段的需要。然而在Abp的默认关系型数据库ORM(即EF Core)中,并没有提供对数据字段按需更新的实现。本博文将在尽量保证通用性的前提下实现一个简单的EF Core按需更新机制。

目标

支持通过给实体标注特性的方式,引导EF Core更新部分字段。本博文将实现一个更新非默认值字段的机制。

思路

在调用DbContextSaveChanges方法进行数据更新时,EF Core的更改跟踪机制会遍历实体字段获取更改状态信息。通过重写AbpDbContextApplyAbpConceptsForModifiedEntity方法,我们可以获得一个在进行数据库交互前修改字段更改状态的横切点。另一方面,通过利用PropertyEntryMetadata属性,我们可以传入字段的自定义元数据,以引导横切点的判断逻辑。

因此,大致步骤如下:

  1. 创建一个标注特性类
  2. 针对新创建的特性类,封装EF Core的EntityTypeBuilder扩展方法,为具有该特性的实体字段添加元数据
  3. 重写AbpDbContextApplyAbpConceptsForModifiedEntity方法,根据实体字段元数据更新字段更改状态

编码

创建特性类PreventUpdateIfDefaultAttribute,用于标注某字段在默认值时不进行更新:

1
2
3
4
public class PreventUpdateIfDefaultAttribute : Attribute
{

}

在扩展库的EntityTypeBuilderExtensions中,添加一对ConfigurePreventUpdateTryConfigurePreventUpdate方法。如果还没有这个扩展方法类,就在适当位置创建一个,后续还会用到。

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实体字段按需更新功能已开发完成。在下一篇博文中,博主会对本文实现的功能编写测试。