快速认识ORM
对象-关系映射,即Object/Relation Mapping,主要实现程序对象到关系数据库的映射。现在.Net比较流行的ORM框架有:EF、SqlSugar、Dapper、FreeSql、Nhibernate、IBatis.Net等。
O/RM只是一层代码的封装,底层还是基于ADO.NET完成对数据库的访问。
一般写法
如果我们要写一个查询,用ADO.NET就会如下这样写。
private static string ConnectionStringCustomers = "Data Source=.;Database=Customers;" + "User ID=sa;Password=123456;MultipleActiveResultSets=True"; public Company FindCompany(int id){ string sql = $@" SELECT [Id],[Name],[CreateTime],[CreatorId], [LastModifierId],[LastModifyTime] FROM [dbo].[Company] WHERE ID = {id}"; using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers)) { SqlCommand command = new SqlCommand(sql, conn); conn.Open(); var reader = command.ExecuteReader(); if (reader.Read()) { Company company = new Company() { Id = (int)reader["Id"], Name = reader["Name"].ToString() }; return company; } else { return null; } }}
public abstract class BaseModel{ public int Id { set; get; }} public class Company : BaseModel{ public string Name { get; set; } public DateTime CreateTime { get; set; } public int CreatorId { get; set; } public int? LastModifierId { get; set; } public DateTime? LastModifyTime { get; set; }}
但这样的写法是写死了的,我们能不能写一个通用查询,不管他是哪张表。
通用查询
既然要通用,那就不能写死类型,我们想到了使用泛型。泛型是任何类型都可以用,为了保证类型正确,我们再加泛型约束。
为了得到属性,我们要使用反射获取。
public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel{ string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.Name}]").ToArray()); string sql = #34;SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}"; using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers)) { SqlCommand command = new SqlCommand(sql, conn); conn.Open(); var reader = command.ExecuteReader(); if (reader.Read()) { // 反射实例化 T t = Activator.CreateInstance<T>(); foreach (var property in typeof(T).GetProperties()) { property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]); } return t; } else { return null; } } return default(T);}
上述的方法,使用泛型和反射使我们的查询可以通用。
然后使用Company Company = sqlHelper.Find<Company>(1);来调用得到实体。
但是,我们还有一个问题,如果我们的表名和实体类名称不一样,或字段名不一样,比如:表名为Sys_Company而实体名为Company,那我们该如何映射?
这里我们打算用C#的特性来解决这一问题。
首先,创建用来映射的特性类。
public class AbstractMappingAttribute : Attribute{ public string MappingName = null; public AbstractMappingAttribute(string mappingName) { this.MappingName = mappingName; }}
映射表名。
[AttributeUsage(AttributeTargets.Class)]public class DBProxyTableAttribute: AbstractMappingAttribute{ public DBProxyTableAttribute(string tableName) : base(tableName){}}
映射列名。
[AttributeUsage(AttributeTargets.Property)] public class DBProxyColumnAttribute : AbstractMappingAttribute { public DBProxyColumnAttribute(string columnName):base(columnName) {} }
在类名上添加特性。
[DBProxyTable("Sys_Company")]public class Company : BaseModel{ [DBProxyColumn("Company_Name")] public string Name { get; set; } ......}
获取实体类名或属性上的特性值来映射数据库的方法。
public static class DBProxyMappingExtension{ public static string GetMappingName(this MemberInfo member) { string name = null; if (member.IsDefined(typeof(AbstractMappingAttribute), true)) { var attribute = member.GetCustomAttribute<AbstractMappingAttribute>(); name = attribute.MappingName; } else { name = member.Name; } return name; }}
最后,重新修改通用方法。
public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel{ //string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.Name}]").ToArray()); string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.GetMappingName()}]").ToArray()); //string sql = #34;SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}"; string sql = #34;SELECT {colums} FROM [{typeof(T).GetMappingName()}] WHERE Id={id}"; using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers)) { SqlCommand command = new SqlCommand(sql, conn); conn.Open(); var reader = command.ExecuteReader(); if (reader.Read()) { // 反射实例化 T t = Activator.CreateInstance<T>(); foreach (var property in typeof(T).GetProperties()) { //property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]); property.SetValue(t, reader[property.GetMappingName()] is DBNull ? null : reader[property.GetMappingName()]); } return t; } else { return null; } } return default(T);}
通用插入
我们先写一个泛型缓存类:
public class SQLCacheBuilder<T> where T : BaseModel{ private static string InsertSQL = ""; /// <summary> /// 静态构造函数,由CLR保障,在第一次使用该类之前,完成调用且只调用一次 /// </summary> static SQLCacheBuilder() { Console.WriteLine("SQLCacheBuilder 静态ctor。。。。。。。"); string columns = string.Join(",", typeof(T).GetPropertiesNoKey() .Select(p => #34;[{p.GetMappingName()}]")); string values = string.Join(",", typeof(T).GetPropertiesNoKey() .Select(p => #34;@{p.GetMappingName()}")); InsertSQL = #34;INSERT INTO [{typeof(T).GetMappingName()}] " + #34;({columns}) VALUES ({values})"; } public static string GetInsertSQL() { return InsertSQL; }}
当第一次调用SQLCacheBuilder方法对InsertSQL赋值,那么再次调用就会直接取缓存中的InsertSQL。但如果调用SQLCacheBuilder<T>类,传来的泛型T不同,则缓存的InsertSQL是不同的。InsertSQL就是我们要执行的sql语句。
我们数据库表设置的id是自增的,为了在插入的SQL中过滤掉Id字段,我们打算用特性过滤。
[AttributeUsage(AttributeTargets.Property)]public class DBProxyKeyAttribute: Attribute{} public static class DBProxyFilterAttributeExtension{ public static IEnumerable<PropertyInfo> GetPropertiesNoKey(this Type type) { return type.GetProperties() .Where(p => !p.IsDefined(typeof(DBProxyKeyAttribute), true)); }}
然后在实体属性id上加上此特性:
[DBProxyKey] public int Id { set; get; }
这样只要调用了GetPropertiesNoKey方法,遇见属性上加有DBProxyKey特性则会过滤掉。
最后,就是我们的通用的插入数据的方法:
// 写一个通用插入方法public bool Insert<T>(T t) where T : BaseModel{ // 调用SQLCacheBuilder方法获得拼接的sql string sql = SQLCacheBuilder<T>.GetInsertSQL(); // 为了防止拼接的有sql注入,使用参数parameters var parameters = typeof(T).GetPropertiesNoKey() .Select(p => new SqlParameter(#34;@{p.GetMappingName()}", p.GetValue(t) is null ? DBNull.Value : p.GetValue(t))); return this.ExecuteSQL<bool>(sql, parameters.ToArray(), command => { int iResult = command.ExecuteNonQuery(); return iResult == 1; });} private T ExecuteSQL<T>(string sql, SqlParameter[] parameters, Func<SqlCommand, T> func){ using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers)) { SqlCommand command = new SqlCommand(sql, conn); command.Parameters.AddRange(parameters); conn.Open(); return func.Invoke(command); }}
本文暂时没有评论,来添加一个吧(●'◡'●)