前言
众所周知,C#中的mongodb驱动默认是不会序列化和反序列只读属性的。所以当我们存储Protobuf的类型时,如果有属性是RepeatedField和MapField类型,那么该属性并不会被存储到mongodb数据库中。如果要正常存储,则需要自己调用RegisterClassMap方法注册该Protobuf类型的映射。类似下面的代码:
class Test
{
public RepeatedField<int> RepeatedField { get; }
}
static Test CreateTest(RepeatedField<int> repeatedField)
{
var res = new Test();
res.RepeatedField.AddRange(repeatedField);
return res;
}
static void RegisterTestClassMap()
{
BsonClassMap.RegisterClassMap<Test>(cm =>
{
cm.AutoMap();
// SetDefaultValue是为了防止Protobuf新加了属性,但是数据库的老数据没有该属性,导致反序列化失败的问题
cm.MapProperty(x => x.RepeatedField).SetDefaultValue(new RepeatedField<int>());
cm.MapCreator(p => CreateTest(p.RepeatedField));
});
}
我们需要为每一个类型都注册类似的映射,非常的麻烦。而且如果protobuf的字段有新增,还需要改动代码,如果修改不及时还会导致线上的数据丢失。麻烦且不安全。
自动映射
我们自定义一个IClassMapConvention,自动生成并注册包含只读属性的类的构造函数的委托。
核心是利用表达式树,自动生成对应类的构造函数的委托。
生成的委托只支持protobuf里的RepeatedField和MapField类型。需要支持其他的需要自己修改。
/// <summary>
/// Protobuf的默认映射(映射只读属性及其默认值和构造函数)
/// </summary>
public class ProtobufDefaultMapConvention : ConventionBase, IClassMapConvention
{
private readonly BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
public ProtobufDefaultMapConvention() { }
private string RepeatedFieldName = typeof(RepeatedField<>).Name;
private string MapFieldName = typeof(MapField<,>).Name;
/// <summary>
/// 应用
/// </summary>
/// <param name="classMap"></param>
public void Apply(BsonClassMap classMap)
{
var readOnlyProperties = classMap
.ClassType
.GetTypeInfo()
.GetProperties(_bindingFlags)
.Where(p => IsReadOnlyProperty(classMap, p))
.ToList();
foreach (var property in readOnlyProperties)
{
//<c cref="RepeatedField{T}">和<c cref="MapField{TKey, TValue}">和值类型所有对象共用同一个默认实例。要自己映射请覆盖默认值。
// 必须设置默认值。否则会无法匹配构造函数
if (property.PropertyType.Name == RepeatedFieldName || property.PropertyType.Name == MapFieldName || property.PropertyType.IsValueType)
{
var defaultVal = Activator.CreateInstance(property.PropertyType);
classMap.MapMember(property).SetDefaultValue(defaultVal);
}
else
{
classMap.MapMember(property).SetDefaultValue(() => Activator.CreateInstance(property.PropertyType));
LogUtil.Warn($"ProtobufDefaultMapConvention->PB发现异常只读类型:{property.PropertyType.Name}");
}
}
var res = GetProtobufCreatorFunc(classMap.ClassType, readOnlyProperties);
classMap.MapCreator(res.Item1, res.Item2);
}
/// <summary>
/// 获取protobuf的构造函数的委托(赋值只读属性)
/// </summary>
/// <param name="targetType"></param>
/// <param name="readOnlyPropList"></param>
/// <returns></returns>
public (Delegate, string[]) GetProtobufCreatorFunc(Type targetType, List<PropertyInfo> readOnlyPropList)
{
// 返回值
var result = Expression.Variable(targetType, "result");
// 初始化要创建的对象
var initializeResult = Expression.Assign(result, Expression.New(targetType));
var ctorParamList = new List<ParameterExpression>();
// 构造函数func的方法体语句
var ctorBlockExperssionList = new List<Expression>() { initializeResult };
// 构造函数func的泛型类型列表
var ctorFuncTypeList = new List<Type>(readOnlyPropList.Count + 1);
// 构造函数func的参数名称列表
var paramNames = new List<string>(readOnlyPropList.Count);
foreach (var item in readOnlyPropList)
{
ctorFuncTypeList.Add(item.PropertyType);
paramNames.Add(item.Name);
// 将需要赋值的只读属性设置为参数
var ctorParam = Expression.Parameter(item.PropertyType, item.Name);
ctorParamList.Add(ctorParam);
// 取要赋值的只读属性
var readOnlyMember = Expression.Property(result, item.Name);
// 赋值语句
Expression methodCall = null;
if (item.PropertyType.Name == RepeatedFieldName)
{
// 如果传入的参数不为null的话,则调用RepeatedField的AddRange方法
//methodCall = Expression.IfThen(Expression.NotEqual(ctorParam, Expression.Constant(null)), Expression.Call(readOnlyMember, nameof(RepeatedField<int>.AddRange), null, ctorParam));
// 不判null,如果要自己设置默认值请勿设置为null
methodCall = Expression.Call(readOnlyMember, nameof(RepeatedField<int>.AddRange), null, ctorParam);
}
else if (item.PropertyType.Name == MapFieldName)
{
// 如果传入的参数不为null的话,则调用MapField的Add方法
//methodCall = Expression.IfThen(Expression.NotEqual(ctorParam, Expression.Constant(null)), Expression.Call(readOnlyMember, nameof(MapField<string, int>.Add), null, ctorParam));
// 不判null,如果要自己设置默认值请勿设置为null
methodCall = Expression.Call(readOnlyMember, nameof(MapField<string, int>.Add), null, ctorParam);
}
if (methodCall != null)
{
ctorBlockExperssionList.Add(methodCall);
}
}
ctorFuncTypeList.Add(targetType);
ctorBlockExperssionList.Add(result);
// 构造函数func的方法块
var ctorFuncBody = Expression.Block(new[] { result }, ctorBlockExperssionList);
// 构造函数func的Type
var ctorFuncType = Expression.GetFuncType(ctorFuncTypeList.ToArray());
// 构造函数func的lambda表达式
var ctorlambda = Expression.Lambda(ctorFuncType, ctorFuncBody, $"create{targetType.Name}", true, ctorParamList).Compile();
return (ctorlambda, paramNames.ToArray());
}
/// <summary>
/// 只读属性
/// </summary>
/// <param name="classMap"></param>
/// <param name="propertyInfo"></param>
/// <returns></returns>
private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo)
{
if (!propertyInfo.CanRead) return false;
if (propertyInfo.CanWrite) return false; // already handled by default convention
if (propertyInfo.GetIndexParameters().Length != 0) return false; // skip indexers
var getMethodInfo = propertyInfo.GetMethod;
if (getMethodInfo == null)
{
return false;
}
// skip overridden properties (they are already included by the base class)
if (getMethodInfo.IsVirtual && getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType) return false;
return true;
}
}
最后注册该Convention
ConventionRegistry.Register("PBConventionsPack", new ConventionPack { new ProtobufDefaultMapConvention() }, t => t.IsAssignableTo(typeof(IMessage)) && t is { IsAbstract: false });
文章摘自:https://www.cnblogs.com/qwfy-y/p/18311993
