【前言】

  前面讲过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

轻量级ORM框架 Bankinate的更多相关文章

  1. 轻量级ORM框架初探-Dapper与PetaPoco的基本使用

    一.EntityFramework EF是传统的ORM框架,也是一个比较重量级的ORM框架.这里仍然使用EF的原因在于为了突出轻量级ORM框架的性能,所谓有对比才有更优的选择. 1.1 准备一张数据库 ...

  2. c# 轻量级ORM框架 实现(一)

    发布一个自己写的一个轻量级ORM框架,本框架设计期初基于三层架构.所以从命名上来看,了解三层的朋友会很好理解. 设计该框架的目的:不想重复的写增删改查,把精力放到功能实现上. 发布改框架的原因:希望给 ...

  3. 轻量级ORM框架 QX_Frame.Bantina(二、框架使用方式介绍)

    轻量级ORM框架QX_Frame.Bantina系列讲解(开源) 一.框架简介 http://www.cnblogs.com/qixiaoyizhan/p/7417467.html 二.框架使用方式介 ...

  4. 轻量级ORM框架 QX_Frame.Bantina(一、框架简介)

    轻量级ORM框架QX_Frame.Bantina系列讲解(开源) 一.框架简介 http://www.cnblogs.com/qixiaoyizhan/p/7417467.html 二.框架使用方式介 ...

  5. .NET轻量级ORM框架Dapper入门精通

    一.课程介绍 本次分享课程包含两个部分<.NET轻量级ORM框架Dapper修炼手册>和<.NET轻量级ORM框架Dapper葵花宝典>,阿笨将带领大家一起领略轻量级ORM框架 ...

  6. 轻量级ORM框架Dapper应用一:Dapper安装

    一.Dapper简介 Dapper是一款轻量级ORM框架,为解决网站访问流量极高而产生的性能问题而构造,主要通过执行TSQL表达式而实现数据库的CQRS. 如果你在项目中遇到性能访问问题,选择Dapp ...

  7. 分享自己写的基于Dapper的轻量级ORM框架~

    1.说明 本项目是一个使用.NET Standard 2.0开发的,基于 Dapper 的轻量级 ORM 框架,包含基本的CRUD以及根据表达式进行一些操作的方法,目前只针对单表,不包含多表连接操作. ...

  8. C# 性能优化 之 秒表 Stopwatch。 Dapper一个和petapoco差不多的轻量级ORM框架

    Sweet小马 小马同学的编程日记. C# 性能优化 之 秒表 Stopwatch. 生词解释:Diagnostics[,daɪəg'nɑstɪks] n.诊断学 using System.Diagn ...

  9. c# 轻量级 ORM 框架 之 Model解析 (四)

    关于orm框架设计,还有必要说的或许就是Model解析了,也是重要的一个环节,在实现上还是相对比较简单的. Model解析,主要用到的技术是反射了,即:把类的属性与表的字段做映射. 把自己的设计及实现 ...

随机推荐

  1. ROS笔记1 安装及创建一个ROS Package

    安装 跟着官方的安装指引来就行了.安装前要先确定自己的ros版本和ubuntu版本.这二者是一一对应的. http://wiki.ros.org/ROS/Installation 主要是package ...

  2. HttpClient post提交数据,返回json

    // string data = "{\"uid\":515,\"timestamp\":\"2018 - 5 - 25 19:05:00\ ...

  3. Java开发笔记(六十三)双冒号标记的方法引用

    前面介绍了如何自己定义函数式接口,本文接续函数式接口的实现原理,阐述它在数组处理中的实际应用.数组工具Arrays提供了sort方法用于数组元素排序,可是并未提供更丰富的数组加工操作,比如从某个字符串 ...

  4. Dynamics 365-如何指定邮件收件人的地址

    如果CRM Entity勾选了Sending Email,那么这个Entity的Records是可以直接作为Email的收件人使用的,而邮件地址是Entity的Primary Email字段. 在我们 ...

  5. 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上

    在.NET主程序中,我们可以通过创建 ExcelApplication 对象来打开一个Excel应用程序,如果我们想在Excle里面再打开WPF窗口,问题就不那么简单了. 我们可以简单的实例化一个WP ...

  6. Fragment 生命周期的详情

    Fragment每个生命周期方法的意义.作用(注意红色的不是生命周期方法):setUserVisibleHint():设置Fragment可见或者不可见时会调用此方法.在该方法里面可以通过调用getU ...

  7. PHP如何实现在数据库随机获取几条记录

    本文实例讲述了PHP实现在数据库百万条数据中随机获取20条记录的方法.PHP实例分享给大家供大家参考,具体如下: 为什么要写这个? 在去某个公司面试时,让写个算法出来,当时就蒙了,我开发过程中用到算法 ...

  8. Python使用Plotly绘图工具,绘制散点图、线形图

    今天在研究Plotly绘制散点图的方法 使用Python3.6 + Plotly Plotly版本2.0.0 在开始之前先说说,还需要安装库Numpy,安装方法在我的另一篇博客中有写到:https:/ ...

  9. reStructuredText文件语法简单学习

    reStructuredText 是一种扩展名为.rst的纯文本文件,通过特定的解释器,能够将文本中的内容输出为特定的格式 1. 章节标题 章节头部由下线(也可有上线)和包含标点的标题组合创建,其中下 ...

  10. ffmpeg错误码

    以下ffmpeg错误代码及翻译是本人遇到或发现后整理出来的,不保证包含全部错误代码 EPERM(不允许操作,无相应权限) = -1 ENOENT(文件或目录不存在) = -2 ESRCH(线程不存在) ...