【前言】

  前面讲过ORM的前世今生,对ORM框架不了解的朋友可以参考博文:https://www.cnblogs.com/7tiny/p/9551754.html

  今天,我们主要通过设计一款轻量级的ORM框架来介绍:"如何实现一个ORM框架"

  文末给出了GitHub源码地址~

【基本要素】

  既然是ORM框架,那么必不可或缺的三点:

  1.Sql语句的自动生成

  2.数据结果集自动映射到类型实体

  3.多数据库的支持

  甚至可以在此三点的基础上扩展出更多的:

  1.缓存处理

  2.Api的封装

  3.日志系统

  基于以上几点,那么我们逐步开始我们的设计:

  为了功能抽象和细化的职责划分,我们将各个功能点拆分成为各个组件,灵活进行装配。

   

  数据存储核心:调用底层数据库驱动执行Sql语句,将数据持久化

  表映射描述器:描述表和实体的映射关系

  Sql语句转化器:将封装的数据操作Api转化成对应数据库的Sql语句

  数据操作上下文:用户数据操作信息传递,包装,数据库连接管理等,缓存核心配置信息的承载

  缓存核心:用户ORM框架的缓存支持(一级缓存/二级缓存)

【实现细节】

  我们抽象出核心功能组件后,对各个功能组件进行详细设计:

  数据存储核心:

   

  数据存储核心主要包括对多种数据库驱动的封装调用,读写分离的简单策略,查询数据集合与强类型实体的映射(性能优化点,目前采用Expression 表达式树缓存委托方式)。

  这里以封装的支持多种关系型数据库的DbHelper形式呈现

 /*********************************************************
* CopyRight: 7TINY CODE BUILDER.
* Version: 5.0.0
* Author: 7tiny
* Address: Earth
* Create: 2018-04-19 21:34:01
* Modify: 2018-04-19 21:34:01
* E-mail: dong@7tiny.com | sevenTiny@foxmail.com
* GitHub: https://github.com/sevenTiny
* Personal web site: http://www.7tiny.com
* Technical WebSit: http://www.cnblogs.com/7tiny/
* Description:
* Thx , Best Regards ~
*********************************************************/
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks; namespace SevenTiny.Bantina.Bankinate
{
public enum DataBaseType
{
SqlServer,
MySql,
Oracle,
MongoDB
}
public abstract class DbHelper
{
#region ConnString 链接字符串声明 /// <summary>
/// 连接字符串 ConnString_Default 默认,且赋值时会直接覆盖掉读写
/// </summary>
private static string _connString;
public static string ConnString_Default
{
get { return _connString; }
set
{
_connString = value;
ConnString_RW = _connString;
ConnString_R = _connString;
}
}
/// <summary>
/// 连接字符串 ConnString_RW 读写数据库使用
/// </summary>
public static string ConnString_RW { get; set; } = _connString;
/// <summary>
/// 连接字符串 ConnString_R 读数据库使用
/// </summary>
public static string ConnString_R { get; set; } = _connString;
/// <summary>
/// DataBaseType Select default:mysql
/// </summary>
public static DataBaseType DbType { get; set; } = DataBaseType.MySql; #endregion #region ExcuteNonQuery 执行sql语句或者存储过程,返回影响的行数---ExcuteNonQuery
public static int ExecuteNonQuery(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
return cmd.DbCommand.ExecuteNonQuery();
}
}
}
public static int ExecuteNonQuery(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);//参数增加了commandType 可以自己编辑执行方式
return cmd.DbCommand.ExecuteNonQuery();
}
}
}
public static void BatchExecuteNonQuery(IEnumerable<BatchExecuteModel> batchExecuteModels)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
foreach (var item in batchExecuteModels)
{
PreparCommand(conn.DbConnection, cmd.DbCommand, item.CommandTextOrSpName, item.CommandType, item.ParamsDic);
cmd.DbCommand.ExecuteNonQuery();
}
}
}
}
public static Task<int> ExecuteNonQueryAsync(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
return cmd.DbCommand.ExecuteNonQueryAsync();
}
}
}
public static Task<int> ExecuteNonQueryAsync(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);//参数增加了commandType 可以自己编辑执行方式
return cmd.DbCommand.ExecuteNonQueryAsync();
}
}
}
public static void BatchExecuteNonQueryAsync(IEnumerable<BatchExecuteModel> batchExecuteModels)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
foreach (var item in batchExecuteModels)
{
PreparCommand(conn.DbConnection, cmd.DbCommand, item.CommandTextOrSpName, item.CommandType, item.ParamsDic);
cmd.DbCommand.ExecuteNonQueryAsync();
}
}
}
}
#endregion #region ExecuteScalar 执行sql语句或者存储过程,执行单条语句,返回单个结果---ScalarExecuteScalar
public static object ExecuteScalar(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
return cmd.DbCommand.ExecuteScalar();
}
}
}
public static object ExecuteScalar(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);
return cmd.DbCommand.ExecuteScalar();
} }
}
public static Task<object> ExecuteScalarAsync(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
return cmd.DbCommand.ExecuteScalarAsync();
}
}
}
public static Task<object> ExecuteScalarAsync(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);
return cmd.DbCommand.ExecuteScalarAsync();
} }
}
#endregion #region ExecuteReader 执行sql语句或者存储过程,返回DataReader---DataReader
public static DbDataReader ExecuteReader(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
//sqlDataReader不能用using 会关闭conn 导致不能获取到返回值。注意:DataReader获取值时必须保持连接状态
SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW);
DbCommandCommon cmd = new DbCommandCommon(DbType);
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
return cmd.DbCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
public static DbDataReader ExecuteReader(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
//sqlDataReader不能用using 会关闭conn 导致不能获取到返回值。注意:DataReader获取值时必须保持连接状态
SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW);
DbCommandCommon cmd = new DbCommandCommon(DbType);
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);
return cmd.DbCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
#endregion #region ExecuteDataTable 执行sql语句或者存储过程,返回一个DataTable---DataTable /**
* Update At 2017-3-2 14:58:45
* Add the ExecuteDataTable Method into Sql_Helper_DG
**/
public static DataTable ExecuteDataTable(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand))
{
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables.Count > )
{
return ds.Tables[];
}
return default(DataTable);
}
}
}
}
public static DataTable ExecuteDataTable(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);
using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand))
{
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables.Count > )
{
return ds.Tables[];
}
return default(DataTable);
}
}
}
}
#endregion #region ExecuteDataSet 执行sql语句或者存储过程,返回一个DataSet---DataSet
public static DataSet ExecuteDataSet(string commandTextOrSpName, CommandType commandType = CommandType.Text)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType);
using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand))
{
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}
}
}
}
public static DataSet ExecuteDataSet(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary)
{
using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW))
{
using (DbCommandCommon cmd = new DbCommandCommon(DbType))
{
PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);
using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand))
{
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}
}
}
}
#endregion #region ExecuteList Entity 执行sql语句或者存储过程,返回一个List<T>---List<T>
public static List<Entity> ExecuteList<Entity>(string commandTextOrSpName, CommandType commandType = CommandType.Text) where Entity : class
{
return GetListFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType));
}
public static List<Entity> ExecuteList<Entity>(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) where Entity : class
{
return GetListFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType, dictionary));
}
#endregion #region ExecuteEntity 执行sql语句或者存储过程,返回一个Entity---Entity
public static Entity ExecuteEntity<Entity>(string commandTextOrSpName, CommandType commandType = CommandType.Text) where Entity : class
{
return GetEntityFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType));
}
public static Entity ExecuteEntity<Entity>(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) where Entity : class
{
return GetEntityFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType, dictionary));
}
#endregion #region ---PreparCommand 构建一个通用的command对象供内部方法进行调用---
private static void PreparCommand(DbConnection conn, DbCommand cmd, string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary = null)
{
//打开连接
if (conn.State != ConnectionState.Open)
{
conn.Open();
} //设置SqlCommand对象的属性值
cmd.Connection = conn;
cmd.CommandType = commandType;
cmd.CommandText = commandTextOrSpName;
cmd.CommandTimeout = ; if (dictionary != null)
{
cmd.Parameters.Clear();
DbParameter[] parameters;
switch (conn)
{
case SqlConnection s:
parameters = new SqlParameter[dictionary.Count];
break;
case MySqlConnection m:
parameters = new MySqlParameter[dictionary.Count];
break;
//case OracleConnection o:
//parameters = new OracleParameter[dictionary.Count];
//break;
default:
parameters = new SqlParameter[dictionary.Count];
break;
} string[] keyArray = dictionary.Keys.ToArray();
object[] valueArray = dictionary.Values.ToArray(); for (int i = ; i < parameters.Length; i++)
{
switch (conn)
{
case SqlConnection s:
parameters[i] = new SqlParameter(keyArray[i], valueArray[i]);
break;
case MySqlConnection m:
parameters[i] = new MySqlParameter(keyArray[i], valueArray[i]);
break;
//case OracleConnection o:
// parameters[i] = new OracleParameter(keyArray[i], valueArray[i]);
// break;
default:
parameters[i] = new SqlParameter(keyArray[i], valueArray[i]);
break;
}
}
cmd.Parameters.AddRange(parameters);
}
}
#endregion #region 通过Model反射返回结果集 Model为 Entity 泛型变量的真实类型---反射返回结果集
public static List<Entity> GetListFromDataSet<Entity>(DataSet ds) where Entity : class
{
List<Entity> list = new List<Entity>();//实例化一个list对象
PropertyInfo[] propertyInfos = typeof(Entity).GetProperties(); //获取T对象的所有公共属性 DataTable dt = ds.Tables[];//获取到ds的dt
if (dt.Rows.Count > )
{
//判断读取的行是否>0 即数据库数据已被读取
foreach (DataRow row in dt.Rows)
{
Entity model1 = System.Activator.CreateInstance<Entity>();//实例化一个对象,便于往list里填充数据
foreach (PropertyInfo propertyInfo in propertyInfos)
{
try
{
//遍历模型里所有的字段
if (row[propertyInfo.Name] != System.DBNull.Value)
{
//判断值是否为空,如果空赋值为null见else
if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换
NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType);
//将convertsionType转换为nullable对的基础基元类型
propertyInfo.SetValue(model1, Convert.ChangeType(row[propertyInfo.Name], nullableConverter.UnderlyingType), null);
}
else
{
propertyInfo.SetValue(model1, Convert.ChangeType(row[propertyInfo.Name], propertyInfo.PropertyType), null);
}
}
else
{
propertyInfo.SetValue(model1, null, null);//如果数据库的值为空,则赋值为null
}
}
catch (Exception)
{
propertyInfo.SetValue(model1, null, null);//如果数据库的值为空,则赋值为null
}
}
list.Add(model1);//将对象填充到list中
}
}
return list;
}
public static List<Entity> GetListFromDataSetV2<Entity>(DataSet ds) where Entity : class
{
List<Entity> list = new List<Entity>();
DataTable dt = ds.Tables[];
if (dt.Rows.Count > )
{
foreach (DataRow row in dt.Rows)
{
Entity entity = FillAdapter<Entity>.AutoFill(row);
list.Add(entity);
}
}
return list;
}
public static Entity GetEntityFromDataReader<Entity>(DbDataReader reader) where Entity : class
{
Entity model = System.Activator.CreateInstance<Entity>(); //实例化一个T类型对象
PropertyInfo[] propertyInfos = model.GetType().GetProperties(); //获取T对象的所有公共属性
using (reader)
{
if (reader.Read())
{
foreach (PropertyInfo propertyInfo in propertyInfos)
{
//遍历模型里所有的字段
if (reader[propertyInfo.Name] != System.DBNull.Value)
{
//判断值是否为空,如果空赋值为null见else
if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换
NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType);
//将convertsionType转换为nullable对的基础基元类型
propertyInfo.SetValue(model, Convert.ChangeType(reader[propertyInfo.Name], nullableConverter.UnderlyingType), null);
}
else
{
propertyInfo.SetValue(model, Convert.ChangeType(reader[propertyInfo.Name], propertyInfo.PropertyType), null);
}
}
else
{
propertyInfo.SetValue(model, null, null);//如果数据库的值为空,则赋值为null
}
}
return model;//返回T类型的赋值后的对象 model
}
}
return default(Entity);//返回引用类型和值类型的默认值0或null
}
public static Entity GetEntityFromDataSet<Entity>(DataSet ds) where Entity : class
{
return GetListFromDataSet<Entity>(ds).FirstOrDefault();
}
public static Entity GetEntityFromDataSetV2<Entity>(DataSet ds) where Entity : class
{
DataTable dt = ds.Tables[];// 获取到ds的dt
if (dt.Rows.Count > )
{
return FillAdapter<Entity>.AutoFill(dt.Rows[]);
}
return default(Entity);
}
#endregion
} /// <summary>
/// Auto Fill Adapter
/// </summary>
/// <typeparam name="Entity"></typeparam>
internal class FillAdapter<Entity>
{
private static readonly Func<DataRow, Entity> funcCache = GetFactory();
public static Entity AutoFill(DataRow row)
{
return funcCache(row);
}
private static Func<DataRow, Entity> GetFactory()
{
var type = typeof(Entity);
var rowType = typeof(DataRow);
var rowDeclare = Expression.Parameter(rowType, "row");
var instanceDeclare = Expression.Parameter(type, "t");
//new Student()
var newExpression = Expression.New(type);
//(t = new Student())
var instanceExpression = Expression.Assign(instanceDeclare, newExpression);
//row == null
var nullEqualExpression = Expression.NotEqual(rowDeclare, Expression.Constant(null));
var containsMethod = typeof(DataColumnCollection).GetMethod("Contains");
var indexerMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier() });
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var setExpressions = new List<Expression>();
//row.Table.Columns
var columns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
foreach (var propertyInfo in properties)
{
if (propertyInfo.CanWrite)
{
//Id,Id is a property of Entity
var propertyName = Expression.Constant(propertyInfo.Name, typeof(string));
//row.Table.Columns.Contains("Id")
var checkIfContainsColumn = Expression.Call(columns, containsMethod, propertyName);
//t.Id
var propertyExpression = Expression.Property(instanceDeclare, propertyInfo);
//row.get_Item("Id")
var value = Expression.Call(rowDeclare, indexerMethod, propertyName);
//t.Id = Convert(row.get_Item("Id"), Int32)
var propertyAssign = Expression.Assign(propertyExpression, Expression.Convert(value, propertyInfo.PropertyType));
//t.Id = default(Int32)
var propertyAssignDefault = Expression.Assign(propertyExpression, Expression.Default(propertyInfo.PropertyType));
//if(row.Table.Columns.Contains("Id")&&!value.Equals(DBNull.Value<>)) {t.Id = Convert(row.get_Item("Id"), Int32)}else{t.Id = default(Int32)}
var checkRowNull = Expression.IfThenElse(Expression.AndAlso(checkIfContainsColumn, Expression.NotEqual(value, Expression.Constant(System.DBNull.Value))), propertyAssign, propertyAssignDefault);
//var checkContains = Expression.IfThen(checkIfContainsColumn, propertyAssign);
setExpressions.Add(checkRowNull);
}
}
var checkIfRowIsNull = Expression.IfThen(nullEqualExpression, Expression.Block(setExpressions));
var body = Expression.Block(new[] { instanceDeclare }, instanceExpression, checkIfRowIsNull, instanceDeclare);
return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
}
} /**
* author:qixiao
* time:2017-9-18 18:02:23
* description:safe create sqlconnection support
* */
internal class SqlConnection_RW : IDisposable
{
/// <summary>
/// SqlConnection
/// </summary>
public DbConnection DbConnection { get; set; } public SqlConnection_RW(DataBaseType dataBaseType, string ConnString_RW)
{
this.DbConnection = GetDbConnection(dataBaseType, ConnString_RW);
}
/**
* if read db disabled,switchover to read write db immediately
* */
public SqlConnection_RW(DataBaseType dataBaseType, string ConnString_R, string ConnString_RW)
{
try
{
this.DbConnection = GetDbConnection(dataBaseType, ConnString_R);
}
catch (Exception)
{
this.DbConnection = GetDbConnection(dataBaseType, ConnString_RW);
}
} /// <summary>
/// GetDataBase ConnectionString by database type and connection string -- private use
/// </summary>
/// <param name="dataBaseType"></param>
/// <param name="ConnString"></param>
/// <returns></returns>
private DbConnection GetDbConnection(DataBaseType dataBaseType, string ConnString)
{
switch (dataBaseType)
{
case DataBaseType.SqlServer:
return new SqlConnection(ConnString);
case DataBaseType.MySql:
return new MySqlConnection(ConnString);
case DataBaseType.Oracle:
//return new OracleConnection(ConnString);
default:
return new SqlConnection(ConnString);
}
}
/// <summary>
/// Must Close Connection after use
/// </summary>
public void Dispose()
{
if (this.DbConnection != null)
{
this.DbConnection.Dispose();
}
}
}
/// <summary>
/// Common sqlcommand
/// </summary>
internal class DbCommandCommon : IDisposable
{
/// <summary>
/// common dbcommand
/// </summary>
public DbCommand DbCommand { get; set; }
public DbCommandCommon(DataBaseType dataBaseType)
{
this.DbCommand = GetDbCommand(dataBaseType);
} /// <summary>
/// Get DbCommand select database type
/// </summary>
/// <param name="dataBaseType"></param>
/// <returns></returns>
private DbCommand GetDbCommand(DataBaseType dataBaseType)
{
switch (dataBaseType)
{
case DataBaseType.SqlServer:
return new SqlCommand();
case DataBaseType.MySql:
return new MySqlCommand();
case DataBaseType.Oracle:
//return new OracleCommand();
default:
return new SqlCommand();
}
}
/// <summary>
/// must dispose after use
/// </summary>
public void Dispose()
{
if (this.DbCommand != null)
{
this.DbCommand.Dispose();
}
}
}
/// <summary>
/// DbDataAdapterCommon
/// </summary>
internal class DbDataAdapterCommon : DbDataAdapter, IDisposable
{
public DbDataAdapter DbDataAdapter { get; set; }
public DbDataAdapterCommon(DataBaseType dataBaseType, DbCommand dbCommand)
{
//get dbAdapter
this.DbDataAdapter = GetDbAdapter(dataBaseType, dbCommand);
//provid select command
this.SelectCommand = dbCommand;
}
private DbDataAdapter GetDbAdapter(DataBaseType dataBaseType, DbCommand dbCommand)
{
switch (dataBaseType)
{
case DataBaseType.SqlServer:
return new SqlDataAdapter();
case DataBaseType.MySql:
return new MySqlDataAdapter();
case DataBaseType.Oracle:
//return new OracleDataAdapter();
default:
return new SqlDataAdapter();
}
}
/// <summary>
/// must dispose after use
/// </summary>
public new void Dispose()
{
if (this.DbDataAdapter != null)
{
this.DbDataAdapter.Dispose();
}
}
} /// <summary>
/// 用于批量操作的批量操作实体
/// </summary>
public class BatchExecuteModel
{
/// <summary>
/// 执行的语句或者存储过程名称
/// </summary>
public string CommandTextOrSpName { get; set; }
/// <summary>
/// 执行类别,默认执行sql语句
/// </summary>
public CommandType CommandType { get; set; } = CommandType.Text;
/// <summary>
/// 执行语句的参数字典
/// </summary>
public IDictionary<string, object> ParamsDic { get; set; }
}
}

DbHelper

  表映射描述器:

  

  表映射描述器定义了一系列对实体的标签,以描述该实体和数据库以及数据库表之间的映射关系。除此之外还扩展了对数据库表缓存的描述。

  Sql语句转化器:

  

  实体类+条件 Sql语句转化过程:

  

  Sql语句转化器的功能为将友好查询Api传递的Lambda表达式语句转化成对应功能的Sql条件语句,以及对应不同数据库生成针对数据库的Sql语句。

  数据操作上下文:

  

  数据库操作上下文作为全部数据操作的载体,在DbContext的基础上分离出SqlDbContext和NoSqlDbContext,分别支持关系型数据库和非关系型数据库。在关系型数据库上下文基础上又可以衍生出各种类型的关系型数据库上下文。该设计保证了组件的水平扩容的能力。

  上下文除了维系各种数据库操作的支持以外,还扩展出了缓存组件的强力支持,可以在上下文中设置当前会话的缓存配置项。

  缓存核心:

  缓存核心拓扑:

  

  缓存核心处理流程:

  

  详细缓存策略:

  

  

  缓存的处理逻辑比较细化,组件的缓存统一由缓存管理核心处理,缓存核心分别调用一级缓存和二级缓存处理对象缓存。缓存处理的步骤如下:

  1.判断是否开启了二级缓存,如果未开启,跳过。

  2.如果开启了二级缓存,检查是否存在二级缓存,如果不存在,则判断是否对实体开启表缓存,如果开启,则开启后台线程扫描表,存储表数据为二级缓存。

  3.判断是否存在一级缓存,如果存在,直接返回一级缓存的结果集。

  4.如果一级缓存不存在,则执行查询命令,并写入一级缓存。

  当二级缓存存在时:

  1.拿出二级缓存并对二级缓存执行增删改查操作,并执行对应的增删改操作持久化过程。

  2.后续所有查询优先从二级缓存中获取。

  如果二级缓存开启状态,执行增删改命令的同时,会同步维护持久化数据和二级缓存数据。

  备注:

  二级缓存是针对某表进行的策略,不是针对所有数据库表的,如果数据库表数量太大,则不建议对该表开启二级缓存,以免耗费大量的内存资源。

【SevenTiny.Bantina.Bankinate ORM框架的使用】

  Nuget包源搜索 SevenTiny.Bantina.Bankinate 安装

  

  新建一个数据库上下文类(对应数据库名称,类似EntityFramework的上下文类)

  如果和库名不一致,则使用DataBase标签进行特殊映射。并在上下文类传递链接字符串和一级缓存和二级缓存的开启配置(默认都关闭)。

 

  这里测试使用SqlServer数据库,因此继承了SqlServerDbContext,如果是其他数据库,则继承对应的数据库。

  根据数据库表创建实体类(实体类可以使用代码生成器自动生成,生成器迭代升级中,有需求可以联系博主)。

  

  这里提供了一个Student类(表),并使用 TableCaching 标签指定了该表二级缓存的开启(重载可以配置该表二级缓存时间)。

  Id为主键,并且是自增列。

  Api列表:

  SevenTiny.Bantina.Bankinate ORM框架提供了一系列标准的非标准的数据查询api,api基于Lambda Expression写法,以便习惯了.Net平台Linq的人群很快上手,无学习成本。

 /*********************************************************
* CopyRight: 7TINY CODE BUILDER.
* Version: 5.0.0
* Author: 7tiny
* Address: Earth
* Create: 2018-04-19 23:58:08
* Modify: 2018-04-19 23:58:08
* E-mail: dong@7tiny.com | sevenTiny@foxmail.com
* GitHub: https://github.com/sevenTiny
* Personal web site: http://www.7tiny.com
* Technical WebSit: http://www.cnblogs.com/7tiny/
* Description:
* Thx , Best Regards ~
*********************************************************/
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions; namespace SevenTiny.Bantina.Bankinate
{
/// <summary>
/// 通用的Api接口,具备基础的操作,缓存
/// </summary>
public interface IDbContext : IDisposable, IBaseOerate, ICacheable
{
} /// <summary>
/// 基础操作Api
/// </summary>
public interface IBaseOerate
{
void Add<TEntity>(TEntity entity) where TEntity : class;
void AddAsync<TEntity>(TEntity entity) where TEntity : class;
void Add<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
void AddAsync<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; void Update<TEntity>(Expression<Func<TEntity, bool>> filter, TEntity entity) where TEntity : class;
void UpdateAsync<TEntity>(Expression<Func<TEntity, bool>> filter, TEntity entity) where TEntity : class; void Delete<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class;
void DeleteAsync<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; bool QueryExist<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class;
int QueryCount<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class;
TEntity QueryOne<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class;
List<TEntity> QueryList<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class;
} /// <summary>
/// 执行sql语句扩展Api
/// </summary>
public interface IExecuteSqlOperate
{
void ExecuteSql(string sqlStatement, IDictionary<string, object> parms = null);
void ExecuteSqlAsync(string sqlStatement, IDictionary<string, object> parms = null);
DataSet ExecuteQueryDataSetSql(string sqlStatement, IDictionary<string, object> parms = null);
object ExecuteQueryOneDataSql(string sqlStatement, IDictionary<string, object> parms = null);
TEntity ExecuteQueryOneSql<TEntity>(string sqlStatement, IDictionary<string, object> parms = null) where TEntity : class;
List<TEntity> ExecuteQueryListSql<TEntity>(string sqlStatement, IDictionary<string, object> parms = null) where TEntity : class;
} /// <summary>
/// 分页查询扩展Api
/// </summary>
public interface IQueryPagingOperate
{
List<TEntity> QueryListPaging<TEntity>(int pageIndex, int pageSize, Expression<Func<TEntity, object>> orderBy, Expression<Func<TEntity, bool>> filter, bool isDESC = false) where TEntity : class;
List<TEntity> QueryListPaging<TEntity>(int pageIndex, int pageSize, Expression<Func<TEntity, object>> orderBy, Expression<Func<TEntity, bool>> filter, out int count, bool isDESC = false) where TEntity : class;
} /// <summary>
/// 缓存接口,实现该接口的类必须具备ORM缓存
/// </summary>
public interface ICacheable
{
}
}

  查询全部(可以根据使用场景组装lambda表达式):

  

  新增一条数据:

  

  修改数据:

  

  删除数据:

  

【框架缓存性能测试】

  缓存性能测试的单元测试代码:

  [Theory]
[InlineData()]
[Trait("desc", "无缓存测试")]
public void QueryListWithNoCacheLevel1(int times)
{
int fromCacheTimes = ;
var timeSpan = StopwatchHelper.Caculate(times, () =>
{
using (var db = new SqlServerTestDbContext())
{
var students = db.QueryList<Student>(t => true);
if (db.IsFromCache)
{
fromCacheTimes++;
}
}
});
Trace.WriteLine($"执行查询{times}次耗时:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次从缓存中获取,有{times - fromCacheTimes}次从数据库获取");
//执行查询100次耗时:6576.8009
} [Theory]
[InlineData()]
[Trait("desc", "一级缓存测试")]
[Trait("desc", "测试该用例,请将一级缓存(QueryCache)打开")]
public void QueryListWithCacheLevel1(int times)
{
int fromCacheTimes = ;
var timeSpan = StopwatchHelper.Caculate(times, () =>
{
using (var db = new SqlServerTestDbContext())
{
var students = db.QueryList<Student>(t => true);
if (db.IsFromCache)
{
fromCacheTimes++;
}
}
});
Trace.WriteLine($"执行查询{times}次耗时:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次从缓存中获取,有{times - fromCacheTimes}次从数据库获取");
//执行查询10000次耗时:1598.2349
} [Theory]
[InlineData()]
[Trait("desc", "二级缓存测试")]
[Trait("desc", "测试该用例,请将二级缓存(TableCache)打开,并在对应表的实体上添加缓存标签")]
public void QueryListWithCacheLevel2(int times)
{
int fromCacheTimes = ;
var timeSpan = StopwatchHelper.Caculate(times, () =>
{
using (var db = new SqlServerTestDbContext())
{
var students = db.QueryList<Student>(t => true);
if (db.IsFromCache)
{
fromCacheTimes++;
}
}
});
Trace.WriteLine($"执行查询{times}次耗时:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次从缓存中获取,有{times - fromCacheTimes}次从数据库获取");
//执行查询10000次耗时:5846.0249,有9999次从缓存中获取,有1次从数据库获取。
//通过更为详细的打点得知,共有两次从数据库获取值。第一次直接按条件查询存在一级缓存,后台线程扫描表存在了二级缓存。
//缓存打点结果:二级缓存没有扫描完毕从一级缓存获取数据,二级缓存扫描完毕则都从二级缓存里面获取数据
} [Theory]
[InlineData()]
[Trait("desc", "开启二级缓存增删改查测试")]
[Trait("desc", "测试该用例,请将二级缓存(TableCache)打开,并在对应表的实体上添加缓存标签")]
public void AddUpdateDeleteQueryCacheLevel2(int times)
{
int fromCacheTimes = ;
var timeSpan = StopwatchHelper.Caculate(times, () =>
{
using (var db = new SqlServerTestDbContext())
{
//查询单个
var stu = db.QueryOne<Student>(t => t.Id == );
//修改单个属性
stu.Name = "test11-1";
db.Update<Student>(t => t.Id == , stu); var students = db.QueryList<Student>(t => true);
if (db.IsFromCache)
{
fromCacheTimes++;
}
}
});
Trace.WriteLine($"执行查询{times}次耗时:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次从缓存中获取,有{times - fromCacheTimes}次从数据库获取");
//执行查询1000次耗时:19102.6441,有1000次从缓存中获取,有0次从数据库获取
//事实上,第一次查询单条的时候已经从数据库扫描并放在了缓存中,后续都是对二级缓存的操作以及二级缓存中查询
}

一级二级缓存测试代码

  不带缓存的查询:

 执行查询100次耗时:6576.8009 ms

  一级缓存开启,二级缓存未开启: 

  执行查询10000次耗时:1598.2349 ms

  一级缓存和二级缓存同时开启:  

  执行查询10000次耗时:5846.0249

  实际上,二级缓存开启以后,最初的查询会走一级缓存。待二级缓存对表扫描结束以后,后续查询将维护二级缓存数据,不再访问数据库表。

【系统展望】

  1.查询api对特定列查询列的支持(性能提升)

  2.对一对多关系的主外键查询支持

  3.更多种类数据库的支持

  4.打点日志的支持

【总结】

  通过本文的一系列分析,不知各位看官对ORM框架的设计思路有没有一个整体的认识。如果在分析结束还没有充分理解设计思路,那么简单粗暴直接上GitHub克隆源码看呗~

  项目基于.NetStandard 2.0构建,因此支持.NetCore2.0以上以及.NetFramework4.6.1以上。对更低版本不兼容。

  虽然原理分析比较冗长,但是代码结构还是非常清晰的,有任何建议,还望不吝赐教,对代码质量的指教非常期待 ^_^

  附源码地址:https://github.com/sevenTiny/SevenTiny.Bantina.Bankinate

最新文章

  1. spring源码分析之cache注解
  2. js压缩
  3. 3D打印公司网站dedecms大气模板
  4. 几种web字体格式
  5. 贪心 HDOJ 4726 Kia&#39;s Calculation
  6. zoj 1097 普吕弗序列
  7. Windows命令行(DOS命令)教程-5 (转载)http://arch.pconline.com.cn//pcedu/rookie/basic/10111/15325_4.html
  8. .Net资源文件全球化
  9. mysql新建数据库时的collation选择(转)
  10. Mac实用操作技巧(六)
  11. hadoop第一课
  12. Window服务器 安装 Memcached
  13. Qt介绍1---QPA(Qt Platform Abstraction)
  14. bzoj3676: [Apio2014]回文串 pam
  15. python脚本中为什么要在目录前加一个r
  16. php程序突然不能用file_get_contents()访问远程网址了?
  17. Kubernetes探索学习005--Kubernetes的Controller模型和ReplicaSet伸缩
  18. 【Python】__slots__ 、@property、多重继承、定制类、枚举类、元类
  19. ResorceGovernor--基础和Demo
  20. python中正则表达式re模块详解

热门文章

  1. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
  2. ubuntu 15.10 设置静态ip 分配固定ip 设置dns 设置网关 命令行配置ip 固定ip不生效怎么办
  3. Ruby Enumerator的各种迭代
  4. C指针和数组的关系详解
  5. ssh转发代理:ssh-agent用法详解
  6. AI书单
  7. 多线程(3)ThreadPool
  8. Ext.define(override)
  9. 2019-01-28 [日常]Beyond的歌里最多是&quot;唏嘘&quot;吗? - Python分词+词频
  10. asp.net/wingtip/显示数据和详细信息