让EFCore更疯狂些的扩展类库(一):通过json文件配置sql语句
前言
EF通过linq和各种扩展方法,再加上实体模型,编写数据库的访问代码确实是优美、舒服,但是生成的sql不尽如意、性能低下,尤其是复杂些的逻辑关系,最终大家还是会回归自然,选择能够友好执行sql语句的ORM,认认真真的编写sql;问题是:EF是否也能够很友好的执行sql语句?EF提供直接执行sql语句的方法并不多,而且也是极其简单的;那是否容易进行扩展?答案是肯定的,在DbContext下提供了Database属性就是为了执行sql用的,然后自己就通过Database下的方法属性进行了扩展(不过最后为了各种数据库的兼容性,使用了DbContext的扩展方法GetService获取相应的服务进行sql语句的执行),以完成这个扩展类库的编写。
扩展类库大体功能简介:
1) sql语句执行器:用于直接执行sql语句
2) EF的查询缓存器:IQueryable(linq) 或 sql语句 的查询缓存,分为本地存储 或 非本地存储(Redis)
a) 缓存存储:永久缓存(不过期) 或者 过期缓存
b) 缓存清理
3) sql配置管理器(让EFCore像MyBatis配置sql,但是通过json配置):加载与管理配置文件中的sql语句
a) sql配置执行器:用于执行配置的sql语句
b) 策略管理器:用于管理策略 与 策略执行器(目前分为三种策略执行器)
i. 策略管理:管理各种策略类型,用于初始化配置文件中的策略配置转换成对象
ii. 策略执行器(一般通过策略对象进行相应的处理)
1. 初始化型的策略执行器
a) 配置策略对象的初始化、替换表名、合并分部sql等的策略执行器
2. sql执行前的策略执行器
a) foreach策略执行器:对SqlParameter或者某些数据类型(list/dictionary/model)进行遍历生成字串替换到sql中
3. sql执行时的策略执行器
a) sql与参数的日志记录策略执行器
b) 查询缓存与清理策略执行器
4) 类库的扩展与优化(因为类库中的各种类是通过DI进行管理的,因此易于扩展与优化)
a) 将查询缓存存储到Redis中
b) 策略与策略执行器的扩展
c) 其他:例如反射帮助类的优化(如果有更好的实现,因为类库内部有不少实现需要通过反射)
源码:
github:https://github.com/skigs/EFCoreExtend
引用类库:
nuget:https://www.nuget.org/packages/EFCoreExtend/
PM> Install-Package EFCoreExtend
查询缓存引用Redis:
PM> Install-Package EFCoreExtend.Redis
类库的使用说明会分好几篇文章进行详细描述,也可参考源码(源码中也有使用测试),类库目前仅支持EFCore 1.1.0,兼容性:MSSqlServer、sqlite、mysql、PostgreSql基本都兼容(EFCore兼容的应该都可以兼容),因为刚完成不久,可能还存在一些bug或不合理的地方,望大家谅解,也请告知。
通过json文件配置sql
Person.json配置文件内容:
{
//"name" : "Person", //设置表名,如果不指定name,那么默认文件名为表名
//配置sql:key为Sql的名称(SqlName,获取配置sql执行器的时候需要根据key获取)
"sqls": {
"GetList": {
//"sql": "select name,birthday,addrid from [Person] where name=@name or id=@id",
"sql": "select name,birthday,addrid from ##tname where name=@name or id=@id", //##tname => 表名
"type": "query"
}
}
}
表的实体模型:
[Table(nameof(Person))]
public class Person
{
public int id { get; set; }
public string name { get; set; }
[Column(TypeName = "datetime")]
public DateTime? birthday { get; set; }
public int? addrid { get; set; }
}
DbContext(MSSqlServer、sqlite、mysql、PostgreSql):
public class MSSqlDBContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured == false)
{
optionsBuilder.UseSqlServer(@"data source=localhost;initial catalog=TestDB;uid=sa;pwd=123;");
}
base.OnConfiguring(optionsBuilder);
}
public DbSet<Person> Person { get; set; }
}
public class SqlieteDBContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured == false)
{
optionsBuilder.UseSqlite(@"data source=./Datas/db.sqlite"); //把/Datas/db.sqlite放到bin下
}
base.OnConfiguring(optionsBuilder);
}
public DbSet<Person> Person { get; set; }
}
public class MysqlDBContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured == false)
{
//SapientGuardian.EntityFrameworkCore.MySql
optionsBuilder.UseMySQL(@"Data Source=localhost;port=3306;Initial Catalog=testdb;user id=root;password=123456;");
}
base.OnConfiguring(optionsBuilder);
}
public DbSet<Person> Person { get; set; }
}
public class PostgreSqlDBContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured == false)
{
optionsBuilder.UseNpgsql(@"User ID=admin;Password=123456;Host=localhost;Port=5432;Database=TestDB;Pooling=true;");
}
base.OnConfiguring(optionsBuilder);
}
public DbSet<Person> Person { get; set; }
}
加载配置文件(在程序初始化的时候调用):
////加载指定的配置文件
//EFHelper.Services.SqlConfigMgr.Config.LoadFile(Directory.GetCurrentDirectory() + "/Person.json");
//加载指定目录下的所有json配置文件
EFHelper.Services.SqlConfigMgr.Config.LoadDirectory(Directory.GetCurrentDirectory() + "/Datas");
获取与调用配置sql的代码:
DbContext db = new MSSqlDBContext();
//获取指定表(配置文件名)的配置信息
var tinfo = db.GetConfigTable<Person>();
//获取指定sql的执行器
var exc = tinfo.GetExecutor(); //使用了CallerMemberNameAttribute,因此会自动获取 方法/属性名 作为参数
var exc1 = tinfo.GetExecutor("GetList"); //这行和上面的一样,"GetList"为在配置文件配置的key
//执行sql:
//方式一:使用SqlParameter传递sql参数
var rtn1 = exc.Query<Person>( //泛型为返回值数据类型
//SqlParams
) },
//返回值类型中需要忽略的属性
new[] { "id" }); //select name,birthday,addrid,并没有加载获取id,因此需要忽略,否则抛异常
//方式二:使用Dictionary传递sql参数
var rtn2 = exc.QueryUseDict<Person>( //泛型为返回值数据类型
//Dictionary => SqlParams
new Dictionary<string, object>
{
{ "name", "tom" },
{ },
},
//返回值类型中需要忽略的属性
new[] { "id" }); //select name,birthday,addrid,并没有加载获取id,因此需要忽略,否则抛异常
//方式三:使用Model传递sql参数
var rtn3 = exc.QueryUseModel<Person>(
//Model => SqlParams
, addrid = },
//参数Model需要忽略的属性
new[] { "addrid" }, //where name=@name or id=@id,并不需要设置addrid
//返回值类型中需要忽略的属性
new[] { "id" }); //select name,birthday,addrid,并没有加载获取id,因此需要忽略,否则抛异常
增删改查sql语句配置内容:
{
//"name" : "Person", //设置表名,如果不指定name,那么默认文件名为表名
"policies": {
////表名策略
//"tname": {
// //"tag": "##tname" //默认值为 ##tname
// "prefix": "[", //前缀
// "suffix": "]" //后缀
//}
},
//配置sql:key为Sql的名称(SqlName,获取配置sql执行器的时候需要根据key获取)
"sqls": {
"GetList": {
//"sql": "select * from [Person] where name=@name",
"sql": "select * from ##tname where name=@name", //##tname => Table Name
"type": "query" //可以不设置,如果设置了会在执行前进行类型检测,
// notsure(默认,不确定),query(查询), nonquery(非查询),scalar,nonexecute(不用于执行的sql,例如分部sql)
},
"GetPerson": {
"sql": "select * from ##tname where name=@name",
"type": "query"
},
"Count": {
"sql": "select count(*) from ##tname",
"type": "scalar"
},
"UpdatePerson": {
"sql": "update ##tname set birthday=@birthday, addrid=@addrid where name=@name",
"type": "nonquery"
},
"AddPerson": {
"sql": "insert into ##tname(name, birthday, addrid) values(@name, @birthday, @addrid) ",
"type": "nonquery"
},
"DeletePerson": {
"sql": "delete from ##tname where name=@name",
"type": "nonquery"
},
//执行存储过程
"ProcQuery": {
"sql": "exec TestQuery @name",
"type": "query"
},
"ProcUpdate": {
"sql": "exec TestUpdate @addrid,@name",
"type": "nonquery"
}
}
}
调用sql配置的代码(包括事物处理):
public class PersonBLL
{
string _name = "tom";
DBConfigTable tinfo;
public PersonBLL(DbContext db)
{
//获取指定表(配置文件名)的配置信息
tinfo = db.GetConfigTable<Person>();
}
public IReadOnlyList<Person> GetList()
{
return tinfo.GetExecutor().QueryUseModel<Person>(
//Model => SqlParams
},
//不需要的SqlParams
new[] { "id" },
//返回值类型需要忽略的属性
new[] { "name" });
}
public int AddPerson()
{
return tinfo.GetExecutor() //获取sql执行器
.NonQueryUseModel(new Person
{
addrid = ,
birthday = DateTime.Now,
name = _name,
}, null);
}
public int UpdatePerson(int? addrid = null)
{
var exc = tinfo.GetExecutor();
return exc.NonQueryUseModel(new { name = _name, birthday = DateTime.Now, addrid = addrid }, null);
}
public int DeletePerson()
{
return tinfo.GetExecutor().NonQueryUseModel(new
{
name = _name
}, null);
}
public int Count()
{
var exc = tinfo.GetExecutor();
var rtn = exc.ScalarUseModel(new { name = _name }, null);
//MSSqlServer返回值会为int,而Sqlite会为long,转换就会出错,因此需要ChangeValueType
return (int)typeof(int).ChangeValueType(rtn);
}
public Person GetPerson()
{
return tinfo.GetExecutor().QueryUseModel<Person>(new
{
name = _name
}, null)?.FirstOrDefault();
}
//执行存储过程
public IReadOnlyList<Person> ProcQuery()
{
////Stored procedure sql:
//create proc TestQuery
//@name varchar(256) = null
//as
//begin
// select * from person where [name] = @name
//end
return tinfo.GetExecutor().QueryUseModel<Person>(new { name = "tom" }, null);
}
//执行存储过程
public int ProcUpdate()
{
////Stored procedure sql:
//create proc TestUpdate
//@addrid int = 0,
//@name varchar(256)
//as
//begin
// update person set addrid = @addrid where[name] = @name
//end
, name = "tom" }, null);
}
//事物
public void DoTran()
{
try
{
//开启事物
tinfo.DB.Database.BeginTransaction();
;
bRtn &= AddPerson() > ;
if (bRtn)
{
tinfo.DB.Database.CommitTransaction(); //提交
}
else
{
tinfo.DB.Database.RollbackTransaction(); //回滚
}
}
catch (Exception ex)
{
tinfo.DB.Database.RollbackTransaction(); //回滚
}
}
}
通过代码设置sql配置:
配置sql除了通过配置文件之外 还可以通过代码进行配置的:
public void AddSqls()
{
EFHelper.Services.SqlConfigMgr.Config.AddSqls<Person>(new Dictionary<string, IConfigSqlInfo>
{
{
"UpdatePerson", //SqlName
new ConfigSqlInfo
{
Sql = $"update {nameof(Person)} set name=@name where id=@id",
Type = ConfigSqlExecuteType.nonquery,
}
},
{
"GetPersonList", //SqlName
new ConfigSqlInfo
{
Sql = $"select * from {nameof(Person)} id=@id",
Type = ConfigSqlExecuteType.query,
}
}
});
}
public void AddTables()
{
EFHelper.Services.SqlConfigMgr.Config.AddOrCombine(new[]
{
new ConfigTableInfo
{
Name = nameof(Person), //表名
Sqls = new Dictionary<string, IConfigSqlInfo>
{
{
"UpdatePerson",
new ConfigSqlInfo
{
Sql = $"update {nameof(Person)} set name=@name where id=@id",
Type = ConfigSqlExecuteType.nonquery,
}
},
{
"GetPersonList",
new ConfigSqlInfo
{
Sql = $"select * from {nameof(Person)} id=@id",
Type = ConfigSqlExecuteType.query,
}
}
}
},
new ConfigTableInfo
{
Name = nameof(Address), //表名
Sqls = new Dictionary<string, IConfigSqlInfo>
{
{
"UpdateAddress",
new ConfigSqlInfo
{
Sql = $"update {nameof(Address)} set fullAddress=@fullAddress where id=@id",
Type = ConfigSqlExecuteType.nonquery,
}
},
{
"GetAddressList",
new ConfigSqlInfo
{
Sql = $"select * from {nameof(Address)} id=@id",
Type = ConfigSqlExecuteType.query,
}
}
}
},
});
}
直接执行sql语句的方法:
如果不想通过配置文件配置sql,而是直接执行sql语句,那么:
DbContext db = new MSSqlDBContext();
var nRtn = db
.NonQueryUseModel($"insert into {nameof(Person)}(name, birthday, addrid) values(@name, @birthday, @addrid)",
//可以使用SqlParameter / Dictionary作为sql的参数(使用Model对象时通过反射转换成SqlParameter的,因此性能会慢些)
new Person
{
name = "tom1",
birthday = DateTime.Now,
addrid = ,
},
//参数Model需要忽略的属性
new[] { "id" });
Assert.True(nRtn > );
var qRtn = db
.QueryUseModel<Person>($"select name, birthday, addrid from {nameof(Person)} where name=@name",
new
{
name = "tom1"
},
//参数Model需要忽略的属性(这里设置为null)
null,
//返回值类型中需要忽略的属性
new[] { "id" });
Assert.True(qRtn?.Count > );
var sRtn = db.ScalarUseModel($"select count(id) from {nameof(Person)} where name=@name", new
{
name = "tom1"
}, null);
Assert.True(();
var nRtn1 = db.NonQueryUseDict($"delete from {nameof(Person)} where name=@name",
new Dictionary<string, object>
{
{"name", "tom1"}
});
Assert.True(nRtn1 > );
执行sql语句的源码:
类库中是如何通过DbContext执行sql语句的,部分源码如下(更详细的可参考github中的源码):
public IReadOnlyList<T> Query<T>(DbContext db, string sql, IDataParameter[] parameters = null,
IReadOnlyCollection<string> ignoreProptNames = null)
where T : new()
{
var concurrencyDetector = db.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var reader = GetReader(db, sql, parameters);
var rtnList = new List<T>();
T model;
object val;
using (reader.DbDataReader)
{
var propts = _objReflec.GetPublicInstancePropts(typeof(T), ignoreProptNames);
while (reader.DbDataReader.Read())
{
model = new T();
foreach (var l in propts)
{
val = reader.DbDataReader[l.Name];
val = ChangeType(l.PropertyType, val);
l.SetValue(model, val);
}
rtnList.Add(model);
}
}
return rtnList;
}
}
/// <summary>
/// 值的类型转换
/// </summary>
protected abstract object ChangeType(Type proptType, object val);
protected RelationalDataReader GetReader(DbContext db, string sql, IDataParameter[] parameters)
{
)
{
//带参数的
var cmd = db.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return cmd
.RelationalCommand
.ExecuteReader(
db.GetService<IRelationalConnection>(),
parameterValues: cmd.ParameterValues);
}
else
{
//不带参数的
var cmd = db.GetService<IRawSqlCommandBuilder>()
.Build(sql);
return cmd
.ExecuteReader(db.GetService<IRelationalConnection>());
}
}
未完待续...
让EFCore更疯狂些的扩展类库(一):通过json文件配置sql语句的更多相关文章
- 让EFCore更疯狂些的扩展类库(二):查询缓存、分部sql、表名替换的策略配置
前言 上一篇介绍了扩展类库的功能简介,通过json文件配置sql语句 和 sql语句的直接执行,这篇开始说明sql配置的策略模块:策略管理器与各种策略的配置. 类库源码:github:https:// ...
- EFCore执行Sql语句的方法:FromSql与ExecuteSqlCommand
前言 在EFCore中执行Sql语句的方法为:FromSql与ExecuteSqlCommand:在EF6中的为SqlQuery与ExecuteSqlCommand,而FromSql和SqlQuery ...
- 让时间处理简单化 【第三方扩展类库org.apache.commons.lang.time】
JAVA的时间日期处理一直是一个比较复杂的问题,大多数程序员都不能很轻松的来处理这些问题.首先Java中关于时间的类,从 JDK 1.1 开始,Date的作用很有限,相应的功能已由Calendar与D ...
- Z.ExtensionMethods 扩展类库
Z.ExtensionMethods 一个强大的开源扩展库 今天有意的在博客园里面搜索了一下 Z.ExtensionMethods 这个扩展类库,确发现只搜到跟这个真正相关的才两篇博文而已,我都点进去 ...
- 怎么让dedecms生成html页面更快些
如何让织梦生成html页面更快些呢? 1.把文章模板里的“相关文章”.“热点文章”.“推荐文章”这类的标记删除了,用其它方式,如:shtml.js 引入 2.把织梦模板里用标记表示的模板路径.php附 ...
- thinkphp5.1 使用第三方扩展类库
此案例介绍的不是通过composer加载的,是手工下载放入extend目录下的扩展类库,仍然以phpspider为例 将owner888目录放入extend目录下,也可以直接将phpspider目录放 ...
- [原创][开源]SunnyUI.Net, C# .Net WinForm开源控件库、工具类库、扩展类库、多页面开发框架
SunnyUI.Net, 基于 C# .Net WinForm 开源控件库.工具类库.扩展类库.多页面开发框架 Blog: https://www.cnblogs.com/yhuse Gitee: h ...
- 为EasySharding.EFCore提供Dapper相关查询扩展
承接上一篇博文中的中间件基本都是写入性的操作,但对于查询操作实际上是比较鸡肋的,如果单纯的查询,没有分表的情况下基本还能适应,这里为了Dapper提供了扩展 Dapper的扩展查询是需要写表名称的,所 ...
- ThinkPHP - 自定义扩展类库
首先在想要使用类库的地方建立文件夹,文件名称随意,不能使用class 然后在配置文件中: 'AUTOLOAD_NAMESPACE' => array( 'Lib' => './Lib', ...
随机推荐
- Html基础详解
HTML是(Hyper Text Mark-up Language)超文本标记语言,是因特网上应用最为广泛的一种网络传输协议,所有的www文件都必须要遵守这个标准.这样就可以让浏览器根据标记语言的规则 ...
- PAT 天梯赛 L1-009 N个数求和
模拟题 题目链接 题解 每次将两个分数进行相加,到最后再将结果化成带分数.主要考察的最大公约数与最小公倍数. 代码如下: #include<cstdio> #include<cstd ...
- Ubuntu Server 重启 Apache Mysql
a. 重启 apache sudo service apache2 restart b. 重启 MySQL sudo service mysql restart
- mac 访问mysql客户端
/usr/local/mysql/bin/mysql -u root -p //mac mysql 管理工具推荐 sequek pro
- CodeForces 625A Guest From the Past
贪心水题 #include <stdio.h> #include <algorithm> #include <string.h> #include <queu ...
- 3个人一起写的EI论文可以检索到啦~ --> Exploring the use of a 3D Virtual Environment in Chinese Cultural Transmission
<a href='http://www.engineeringvillage.com/blog/document.url?mid=cpx_10ed754f14b5b7381b6M764b1017 ...
- 在IOS应用中从竖屏模式强制转换为横屏模式
http://www.cnblogs.com/mrhgw/archive/2012/07/18/2597218.html 在 iPhone 应用里,有时我们想强行把显示模式从纵屏改为横屏(反之亦然), ...
- Android studio开多个窗口引起的问题
1.clean 的时候,intermediates删不掉 2.出现:app:compile_DebugJavaWithJavac 没有具体错误 出现以上问题的时候只要把多余的删除,记得只留一个在当前窗 ...
- 你会做Web上的用户登录功能吗?
Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能.下面的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关 ...
- iOS 获取本地文件的各种坑
1.无论:TXT,EPUB,PDF等各种格式的文件,保存到本地的时候,最好都保存成字母或者数字,不要保存成汉字,否则,在取文件的时候,由于编码的问题,各种瓦特 2.如果文件名真的保存成了汉字,那么进行 ...