C# 统一处理mongodb中Protobuf中只读属性(RepeatedField和MapField)的序列化和反序列化映射

前言

众所周知,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