C#.Net如何手写ORM

[复制链接]
发表于 2022-9-16 17:22:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
快速认识ORM

对象-关系映射,即Object/Relation Mapping,主要实现程序对象到关系数据库的映射。现在.Net比较流行的ORM框架有:EF、SqlSugar、Dapper、FreeSql、Nhibernate、IBatis.Net等。
O/RM只是一层代码的封装,底层还是基于ADO.NET完成对数据库的访问。
一般写法

如果我们要写一个查询,用ADO.NET就会如下这样写。
  1. private static string ConnectionStringCustomers = "Data Source=.;Database=Customers;" +
  2.     "User ID=sa;Password=123456;MultipleActiveResultSets=True";
  3. public Company FindCompany(int id)
  4. {
  5.     string sql = $@"
  6.         SELECT [Id],[Name],[CreateTime],[CreatorId],
  7.                [LastModifierId],[LastModifyTime]
  8.         FROM [dbo].[Company]
  9.         WHERE ID = {id}";
  10.     using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))
  11.     {
  12.         SqlCommand command = new SqlCommand(sql, conn);
  13.         conn.Open();
  14.         var reader = command.ExecuteReader();
  15.         if (reader.Read())
  16.         {
  17.             Company company = new Company()
  18.             {
  19.                 Id = (int)reader["Id"],
  20.                 Name = reader["Name"].ToString()
  21.             };
  22.             return company;
  23.         }
  24.         else
  25.         {
  26.             return null;
  27.         }
  28.     }
  29. }
复制代码
  1. public abstract class BaseModel
  2. {
  3.     public int Id { set; get; }
  4. }   
  5. public class Company : BaseModel
  6. {
  7.     public string Name { get; set; }
  8.     public DateTime CreateTime { get; set; }
  9.     public int CreatorId { get; set; }
  10.     public int? LastModifierId { get; set; }
  11.     public DateTime? LastModifyTime { get; set; }
  12. }
复制代码
但这样的写法是写死了的,我们能不能写一个通用查询,不管他是哪张表。
通用写法

既然要通用,那就不能写死类型,我们想到了使用泛型。泛型是任何类型都可以用,为了保证类型正确,我们再加泛型约束。
为了得到属性,我们要使用反射获取。
  1. public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel
  2. {
  3.     string colums = string.Join(",", typeof(T).GetProperties().Select(p => $"[{p.Name}]").ToArray());
  4.     string sql = $"SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}";
  5.     using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))
  6.     {
  7.         SqlCommand command = new SqlCommand(sql, conn);
  8.         conn.Open();
  9.         var reader = command.ExecuteReader();
  10.         if (reader.Read())
  11.         {
  12.             // 反射实例化
  13.             T t = Activator.CreateInstance<T>();
  14.             foreach (var property in typeof(T).GetProperties())
  15.             {
  16.                 property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]);
  17.             }
  18.             return t;
  19.         }
  20.         else
  21.         {
  22.             return null;
  23.         }
  24.     }
  25.     return default(T);
  26. }
复制代码
上述的方法,使用泛型和反射使我们的查询可以通用。
然后使用Company Company = sqlHelper.Find(1);来调用得到实体。
但是,我们还有一个问题,如果我们的表名和实体类名称不一样,或字段名不一样,比如:表名为Sys_Company而实体名为Company,那我们该如何映射?
这里我们打算用C#的特性来解决这一问题。
首先,创建用来映射的特性类。
  1. public class AbstractMappingAttribute : Attribute
  2. {
  3.     public string MappingName = null;
  4.     public AbstractMappingAttribute(string mappingName)
  5.     {
  6.         this.MappingName = mappingName;
  7.     }
  8. }
复制代码
映射表名。
  1. [AttributeUsage(AttributeTargets.Class)]
  2. public class DBProxyTableAttribute: AbstractMappingAttribute
  3. {
  4.     public DBProxyTableAttribute(string tableName) : base(tableName){}
  5. }
复制代码
映射列名。
  1.     [AttributeUsage(AttributeTargets.Property)]
  2.     public class DBProxyColumnAttribute : AbstractMappingAttribute
  3.     {
  4.         public DBProxyColumnAttribute(string columnName):base(columnName) {}
  5.     }
复制代码
在类名上添加特性。
  1. [DBProxyTable("Sys_Company")]
  2. public class Company : BaseModel
  3. {
  4.     [DBProxyColumn("Company_Name")]
  5.     public string Name { get; set; }
  6.     ......
  7. }
复制代码
获取实体类名或属性上的特性值来映射数据库的方法。
  1. public static class DBProxyMappingExtension
  2. {
  3.     public static string GetMappingName(this MemberInfo member)
  4.     {
  5.         string name = null;
  6.         if (member.IsDefined(typeof(AbstractMappingAttribute), true))
  7.         {
  8.             var attribute = member.GetCustomAttribute<AbstractMappingAttribute>();
  9.             name = attribute.MappingName;
  10.         }
  11.         else
  12.         {
  13.             name = member.Name;
  14.         }
  15.         return name;
  16.     }
  17. }
复制代码
最后,重新修改通用方法。
  1. public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel
  2. {
  3.     //string colums = string.Join(",", typeof(T).GetProperties().Select(p => $"[{p.Name}]").ToArray());
  4.     string colums = string.Join(",", typeof(T).GetProperties().Select(p => $"[{p.GetMappingName()}]").ToArray());
  5.     //string sql = $"SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}";
  6.     string sql = $"SELECT {colums} FROM [{typeof(T).GetMappingName()}] WHERE Id={id}";
  7.     using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))
  8.     {
  9.         SqlCommand command = new SqlCommand(sql, conn);
  10.         conn.Open();
  11.         var reader = command.ExecuteReader();
  12.         if (reader.Read())
  13.         {
  14.             // 反射实例化
  15.             T t = Activator.CreateInstance<T>();
  16.             foreach (var property in typeof(T).GetProperties())
  17.             {
  18.                 //property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]);
  19.                 property.SetValue(t, reader[property.GetMappingName()] is DBNull ? null : reader[property.GetMappingName()]);
  20.             }
  21.             return t;
  22.         }
  23.         else
  24.         {
  25.             return null;
  26.         }
  27.     }
  28.     return default(T);
  29. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表