• Dapper数据库字段(列)与实体属性名不一致,通过Column特性自动注入映射


    一、前言

    Dapper是.NET下一个micro的ORM,它和Entity Framework或Nhibnate不同,属于轻量级的,并且是半自动的。Dapper只有一个代码文件,完全开源,你可以放在项目里的任何位置,来实现数据到对象的ORM操作,体积小速度快。 使用ORM的好处是增、删、改很快,不用自己写sql,因为这都是重复技术含量低的工作,还有就是程序中大量的从数据库中读数据然后创建model,并为model字段赋值。这些ORM都可以轻松给你搞定。ORM给我们开发带来便利时,性能也是一个让我们不得不考虑的问题。一般的ORM性能和直接写原生的sql比都差不少,但是Dapper性能还很错,甚至和DbHelperSQL方式性能高出很多。

    二、问题

    在使用Dapper做查询的时候,数据库字段名和Model属性名一一对应时,直接只用dapper方法是没有问题的,比如:

    1. public class UserModel
    2. {
    3. public int UserId { get; set; }
    4. public string Mobile { get; set; }
    5. public string UserName { get; set; }
    6. }

    但通常数据库的一些规范中,字段名中的单词之间是通过下划线_来拼接的,如

    create_time

    那么,在C#程序中,使用Dapper做查询时,如何配置数据表字段(列)和实体类属性之间的映射呢?

    若属性名和数据库字段不一致(不区分大小写)则查询不出数据,如果使用EF则可以通过Column特性,建立属性和数据表字段之间的映射关系,Dapper则不行,如何让Dapper也支持通过Column特性映射。

    在网上看到其他比较好的解决方案,就是通过SqlMapper.ITypeMap自己手动映射

    但是还是没有实现自动映射,于是基于别人的手动映射我做了一套自动映射的方案,放在文章下方,我们先看一下手动映射的方案:

    以下内容来自博主 Zdelta

    Dapper数据库字段和model属性映射_Zdelta的博客-CSDN博客_dapper映射

    Dapper虽然有colmun的特性,但是并不能完成字段-属性映射;

    我们需要手动拓展一个colmun特性出来,以完成如EF的绑定。

    1,添加一个类ColumnAttributeTypeMapper,用于字段-属性映射:

    1. using Dapper;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Reflection;
    6. namespace 命名空间
    7. {
    8. ///
    9. /// Uses the Name value of the specified to determine
    10. /// the association between the name of the column in the query results and the member to
    11. /// which it will be extracted. If no column mapping is present all members are mapped as
    12. /// usual.
    13. ///
    14. /// The type of the object that this association between the mapper applies to.
    15. public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
    16. {
    17. public ColumnAttributeTypeMapper()
    18. : base(new SqlMapper.ITypeMap[]
    19. {
    20. new CustomPropertyTypeMap(
    21. typeof(T),
    22. (type, columnName) =>
    23. type.GetProperties().FirstOrDefault(prop =>
    24. prop.GetCustomAttributes(false)
    25. .OfType()
    26. .Any(attr => attr.Name == columnName)
    27. )
    28. ),
    29. new DefaultTypeMap(typeof(T))
    30. })
    31. {
    32. }
    33. }
    34. [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    35. public class ColumnAttribute : Attribute
    36. {
    37. public string Name { get; set; }
    38. }
    39. public class FallbackTypeMapper : SqlMapper.ITypeMap
    40. {
    41. private readonly IEnumerable _mappers;
    42. public FallbackTypeMapper(IEnumerable mappers)
    43. {
    44. _mappers = mappers;
    45. }
    46. public ConstructorInfo FindConstructor(string[] names, Type[] types)
    47. {
    48. foreach (var mapper in _mappers)
    49. {
    50. try
    51. {
    52. ConstructorInfo result = mapper.FindConstructor(names, types);
    53. if (result != null)
    54. {
    55. return result;
    56. }
    57. }
    58. catch (NotImplementedException)
    59. {
    60. }
    61. }
    62. return null;
    63. }
    64. public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
    65. {
    66. foreach (var mapper in _mappers)
    67. {
    68. try
    69. {
    70. var result = mapper.GetConstructorParameter(constructor, columnName);
    71. if (result != null)
    72. {
    73. return result;
    74. }
    75. }
    76. catch (NotImplementedException)
    77. {
    78. }
    79. }
    80. return null;
    81. }
    82. public SqlMapper.IMemberMap GetMember(string columnName)
    83. {
    84. foreach (var mapper in _mappers)
    85. {
    86. try
    87. {
    88. var result = mapper.GetMember(columnName);
    89. if (result != null)
    90. {
    91. return result;
    92. }
    93. }
    94. catch (NotImplementedException)
    95. {
    96. }
    97. }
    98. return null;
    99. }
    100. public ConstructorInfo FindExplicitConstructor()
    101. {
    102. return _mappers
    103. .Select(mapper => mapper.FindExplicitConstructor())
    104. .FirstOrDefault(result => result != null);
    105. }
    106. }
    107. }

    2,再添加一个类ColumnMapper,用于添加映射关系:

    1. using Dapper;
    2. using 引入需要的.Models;
    3. namespace 项目命名空间
    4. {
    5. public class ColumnMapper
    6. {
    7. public static void SetMapper()
    8. {
    9. //数据库字段名和c#属性名不一致,手动添加映射关系
    10. SqlMapper.SetTypeMap(typeof(Books), new ColumnAttributeTypeMapper());
    11. //每个需要用到[colmun(Name="")]特性的model,都要在这里添加映射
    12. }
    13. }
    14. }

    3,在starup.cs类的中方法注册:

    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3.     services.AddMvc();
    4.     services.AddSession();
    5.     
    6.     //调用前面的静态方法,将映射关系注册
    7.     ColumnMapper.SetMapper();
    8. }

    4,最后就可以在model的属性名上添加特性来映射到数据库字段名了:

    1. using 引入新加的类.Helper;
    2. public class Books
    3. {
    4. [Column(Name = "create_time")]
    5. public DateTime CreateTime { get; set; }
    6. }

    这样我们就可以在所有的不与数据库对应的model中,方便的添加映射关系了!

    虽然怎样就可以实现了Dapper数据库字段和model属性映射了,但是每个需要用到[colmun(Name="")]特性的model,都需要ColumnMapper.SetMapper()方法中去增加映射,开发起来还是极其的不爽的,有没有办法可以支持自动注入映射呢

    三、我的方案

    在程序启动的时候,通过反射查找出所有的用到[colmun(Name="")]特性的model,自动增加映射

    话不多说直接看代码

    1,添加一个类ColumnAttributeTypeMapper,用于字段-属性映射:

    1. using Dapper;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.ComponentModel.DataAnnotations.Schema;
    5. using System.Linq;
    6. using System.Reflection;
    7. namespace 命名空间
    8. {
    9. public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
    10. {
    11. public ColumnAttributeTypeMapper()
    12. : base(new SqlMapper.ITypeMap[]
    13. {
    14. new CustomPropertyTypeMap(
    15. typeof(T),
    16. (type, columnName) =>
    17. type.GetProperties().FirstOrDefault(prop =>
    18. prop.GetCustomAttributes(false)
    19. .OfType()
    20. .Any(attr => attr.Name == columnName)
    21. )
    22. ),
    23. new DefaultTypeMap(typeof(T))
    24. })
    25. {
    26. }
    27. }
    28. ///
    29. /// 我自定义的映射
    30. ///
    31. public class ColumnAttributeTypeMapper : FallbackTypeMapper
    32. {
    33. public ColumnAttributeTypeMapper(Type type)
    34. : base(new SqlMapper.ITypeMap[]
    35. {
    36. new CustomPropertyTypeMap(
    37. type,
    38. (type, columnName) =>
    39. type.GetProperties().FirstOrDefault(prop =>
    40. prop.GetCustomAttributes(false)
    41. .OfType()
    42. .Any(attr => attr.Name == columnName)
    43. )
    44. ),
    45. new DefaultTypeMap(type)
    46. })
    47. {
    48. }
    49. public ColumnAttributeTypeMapper(Type type, IEnumerable propertyInfos)
    50. : base(new SqlMapper.ITypeMap[]
    51. {
    52. new CustomPropertyTypeMap(type, (type, columnName) =>
    53. propertyInfos.FirstOrDefault(prop =>
    54. (prop.GetCustomAttribute(typeof(ColumnAttribute),false) as ColumnAttribute)?.Name == columnName
    55. )
    56. ),
    57. new DefaultTypeMap(type)
    58. })
    59. {
    60. }
    61. }
    62. public class FallbackTypeMapper : SqlMapper.ITypeMap
    63. {
    64. private readonly IEnumerable _mappers;
    65. public FallbackTypeMapper(IEnumerable mappers)
    66. {
    67. _mappers = mappers;
    68. }
    69. public ConstructorInfo FindConstructor(string[] names, Type[] types)
    70. {
    71. foreach (var mapper in _mappers)
    72. {
    73. try
    74. {
    75. ConstructorInfo result = mapper.FindConstructor(names, types);
    76. if (result != null)
    77. {
    78. return result;
    79. }
    80. }
    81. catch (NotImplementedException)
    82. {
    83. }
    84. }
    85. return null;
    86. }
    87. public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
    88. {
    89. foreach (var mapper in _mappers)
    90. {
    91. try
    92. {
    93. var result = mapper.GetConstructorParameter(constructor, columnName);
    94. if (result != null)
    95. {
    96. return result;
    97. }
    98. }
    99. catch (NotImplementedException)
    100. {
    101. }
    102. }
    103. return null;
    104. }
    105. public SqlMapper.IMemberMap GetMember(string columnName)
    106. {
    107. foreach (var mapper in _mappers)
    108. {
    109. try
    110. {
    111. var result = mapper.GetMember(columnName);
    112. if (result != null)
    113. {
    114. return result;
    115. }
    116. }
    117. catch (NotImplementedException)
    118. {
    119. }
    120. }
    121. return null;
    122. }
    123. public ConstructorInfo FindExplicitConstructor()
    124. {
    125. return _mappers
    126. .Select(mapper => mapper.FindExplicitConstructor())
    127. .FirstOrDefault(result => result != null);
    128. }
    129. }
    130. }

    2,再添加一个类ColumnMapper,用于通过反射查找出所有的用到[colmun(Name="")]特性的model,自动增加映射

    1. using Dapper;
    2. using System;
    3. using System.Collections.Concurrent;
    4. using System.Collections.Generic;
    5. using System.ComponentModel.DataAnnotations.Schema;
    6. using System.Linq;
    7. using System.Reflection;
    8. namespace 命名空间
    9. {
    10. ///
    11. /// 用于通过反射查找出所有的用到[colmun(Name="")]特性的model,自动增加映射
    12. ///
    13. public class ColumnMapper
    14. {
    15. ///
    16. /// 用于通过反射查找出所有的用到[colmun(Name="")]特性的model,自动增加映射
    17. ///
    18. /// 类型的所在程序集
    19. /// 类型命名空间前缀
    20. public static void RegisterColumnAttributeTypeMapper(string assemblyName, string namespaceName)
    21. {
    22. if (!string.IsNullOrEmpty(assemblyName) && !string.IsNullOrEmpty(namespaceName))
    23. {
    24. //二选其一
    25. //1
    26. //var typeList = FindCustomAttributesTypes(assemblyName, dataMapperNamespace);
    27. //typeList.AsParallel().ForAll(type => SqlMapper.SetTypeMap(type, new ColumnAttributeTypeMapper(type)));
    28. //2
    29. var properties= FindCustomAttributesPropertyInfos(assemblyName, namespaceName);
    30. properties.AsParallel().ForAll(item => SqlMapper.SetTypeMap(item.type, new ColumnAttributeTypeMapper(item.type, item.propertyInfos)));
    31. }
    32. }
    33. ///
    34. /// 查找所有类型
    35. ///
    36. /// 类型的所在程序集
    37. /// 类型命名空间前缀
    38. ///
    39. public static IEnumerable FindCustomAttributesTypes(string assemblyName, string namespaceName)
    40. {
    41. var assembly = Assembly.Load(assemblyName);
    42. if (assembly == null)
    43. {
    44. throw new ArgumentNullException("FindTypes assembly");
    45. }
    46. var types = assembly.GetTypes()
    47. .Where(type => !string.IsNullOrEmpty(type.Namespace) && (type.Namespace.Equals(namespaceName) || type.Namespace.StartsWith(namespaceName + ".")))
    48. .Where(item =>
    49. {
    50. var propertyInfoList = item.GetProperties();
    51. return propertyInfoList.Any() && propertyInfoList.Any(p => p.GetCustomAttribute(typeof(ColumnAttribute)) != null);
    52. });
    53. if (!types.Any())
    54. {
    55. throw new ArgumentNullException("FindTypes types");
    56. }
    57. return types;
    58. }
    59. ///
    60. /// 查找所有属性
    61. ///
    62. /// 类型的所在程序集
    63. /// 类型命名空间前缀
    64. ///
    65. ///
    66. public static IEnumerable<(Type type, IEnumerable propertyInfos)> FindCustomAttributesPropertyInfos(string assemblyName, string namespaceName)
    67. {
    68. ConcurrentBag<(Type type, IEnumerable)> properties = new ConcurrentBag<(Type type, IEnumerable)> { };
    69. var assembly = Assembly.Load(assemblyName);
    70. if (assembly == null)
    71. {
    72. throw new ArgumentNullException("FindTypes assembly");
    73. }
    74. assembly.GetTypes()
    75. .Where(type => !string.IsNullOrEmpty(type.Namespace) && (type.Namespace.Equals(namespaceName) || type.Namespace.StartsWith(namespaceName + ".")))
    76. .AsParallel().ForAll(type =>
    77. {
    78. var propertyInfoList = type.GetProperties().Where(p => p.GetCustomAttribute(typeof(ColumnAttribute), false) != null);
    79. if (propertyInfoList.Any())
    80. {
    81. properties.Add((type, propertyInfoList));
    82. SqlMapper.SetTypeMap(type, new ColumnAttributeTypeMapper(type, propertyInfoList));
    83. }
    84. });
    85. return properties;
    86. }
    87. }
    88. }

    3,在Program.cs类或starup.cs 的中注册:

    1. namespace Qxq.Framework.Repository.PostgreSql.Test
    2. {
    3. internal class Program
    4. {
    5. static async Task Main(string[] args)
    6. {
    7. ColumnMapper.RegisterColumnAttributeTypeMapper("类型的所在程序集", "类型命名空间前缀");
    8. }
    9. }
    10. }
    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3. ColumnMapper.RegisterColumnAttributeTypeMapper("类型的所在程序集", "类型命名空间前缀");
    4. }

    4,最后就可以在model的属性名上添加特性来映射到数据库字段名了:

    1. public class UserModel
    2. {
    3. [Column("user_id")]
    4. public int UserId { get; set; }
    5. public string Mobile { get; set; }
    6. [Column("create_date")]
    7. public DateTime CreateDate { get; set; }
    8. }

    没有用到[colmun(Name="")]特性的属性会根据属性名称匹配映射(不区分大小写)

    用到[colmun(Name="")]特性的属性会根据colmun中定义的Name去匹配映射

    这样我们就可以在所有的不与数据库对应的model的属性中,使用[colmun(Name="")]标记数据库字段明,就可以很方便的添加映射关系了!

      

  • 相关阅读:
    使用Python实现微信群发每日一句
    SSM 高校心理测评系统
    处理新连接Acceptor
    服务领域模型
    BLUE引擎变量数据分析
    Flink SQL通过Hudi HMS Catalog读写Hudi并同步Hive表(强烈推荐这种方式)
    1.5.C++项目:仿muduo库实现并发服务器之socket模块的设计
    flink-cdc同步mysql数据到hbase
    m基于QPSK调制解调的无线图像传输matlab仿真,包括扩频解扩均衡等模块
    Talk预告 | Salesforce AI研究院研究科学家徐嘉诚:文本生成中的结构化解码
  • 原文地址:https://blog.csdn.net/qq_27559331/article/details/126420870