Zarf是一个基于.Net轻量级的的ORM库,提供了类似于Linq的查询Api.支持SQLite3及MSSQLSERVER数据库,当前为一个研究项目,请勿用于正式开发.
DbContext是进行数据查询,插入,删除,修改的上下文对象,一切操作都需要在该上下文对象之下进行操作
ConoleApp
using(var db=new DbContext(serviceBuilder=>serviceBuilder.UseSqlServer(ConnectionString)))
{
foreach(var user in db.Query<User>())
{
}
}
AspNetCoreApp
public class MyDbContext : DbContext
{
public MyDbContext(Func<IDbServiceBuilder, IDbService> serviceBuilder)
: base(serviceBuilder)
{
}
}
//StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(p =>new MyDbContext(b => b.UseSqlServer(ConnectionString)));
}
public class User
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public DateTime CreateDay { get; set; }
public string Tel { get; set; }
}
public class Order
{
public int Id { get; set; }
public int UserId { get; set; }
public string Goods { get; set; }
}
IQuery接口没有继承IEnumerable,屏蔽了Linq相关扩展方法的干扰.同时定义了GetEnumerator方法,因此可以使用foreach对IQuery进行迭代访问
public interface IQuery
{
}
public interface IQuery<TEntity> : IQuery
{
IQuery<TEntity> Where(Expression<Func<TEntity, bool>> predicate);
TEntity First();
TEntity First(Expression<Func<TEntity, bool>> predicate);
TEntity FirstOrDefault();
.......
}
db.Query<User>().Select(i => new { i.Id, i.Age, Date = DateTime.Now.Date })
exec sp_executesql N' SELECT [T0].[Id] AS C5,[T0].[Age] AS C6,@P0 AS C7 FROM [User] AS [T0]',N'@P0 datetime',@P0='2018-02-06 00:00:00'
db.Query<User>().Select(i => new { i.Id, i.Age, Date = DateTime.Now.Date })
exec sp_executesql N' SELECT [T0].[Id] AS C5,[T0].[Age] AS C6,@P0 AS C7 FROM [User] AS [T0]',N'@P0 datetime',@P0='2018-02-06 00:00:00'
db.Query<User>().Where(i => i.Id > 10)
exec sp_executesql N' SELECT [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0] WHERE [T0].[Id] > @P0',N'@P0 int',@P0=10
db.Query<User>()
.Join(db.Query<Order>(), (u, o) => u.Id == o.UserId, JoinType.Inner)
.Select((u, o) => new { u.Id, o.Goods });
SELECT [T0].[Id] AS C8,[T1].[Goods] AS C9 FROM [User] AS [T0] Inner JOIN [Order] AS [T1] ON [T0].[Id] = [T1].[UserId]
Join方法后面只能继续调用Join或者Select方法,如果需要调用Where等方法过滤,需要在Join前调用或者在Select之后调用.
提供了InnerJoin,LeftJoin,RightJoin,CorssJoin,FullJoin扩展方法,在子查询中只能使用Join,不能使用InnerJoin等扩展方法
//Age Asc,Id Desc
db.Query<User>().OrderBy(i => i.Age).ThenByDescending(i => i.Id);
SELECT [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0] ORDER BY [T0].[Age] ASC ,[T0].[Id] DESC
//GroupBy,OrderBy中只能范围一个属性,因此先Select,后续提供i=>i,i=>new{i.Id,i.Age}等支持
db.Query<User>().Select(i => new { i.Id }).GroupBy(i => i.Id);
SELECT [T0].[Id] AS C5 FROM [User] AS [T0] GROUP BY [T0].[Id]
db.Query<User>().Take(10);
SELECT TOP 10 [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0]
//使用ROW_NUMBER()函数,sqlite生成Limit,Offset
db.Query<User>().Skip(2);
exec sp_executesql N' SELECT [T1].[C0],[T1].[C1],[T1].[C2],[T1].[C3],[T1].[C4] FROM ( SELECT [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4, ROW_NUMBER() OVER ( ORDER BY GETDATE()) AS __ROWINDEX__ FROM [User] AS [T0] ) AS [T1] WHERE [T1].[__rowIndex__] > @P0',N'@P0 int',@P0=2
db.Query<User>().All(u => u.Id > 0);
exec sp_executesql N' SELECT (SELECT CASE WHEN NOT EXISTS( SELECT @P0 FROM [User] AS [T0] WHERE NOT ( [T0].[Id] > @P1 ) ) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END)',N'@P0 bit,@P1 int',@P0=1,@P1=0
db.Query<User>().Any(u => u.Id > 10);
exec sp_executesql N'SELECT (SELECT CASE WHEN EXISTS( SELECT @P0 FROM [User] AS [T0] WHERE [T0].[Id] > @P1 ) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END)',N'@P0 bit,@P1 int',@P0=1,@P1=10
db.Query<User>().First(i => i.Id == 3);
db.Query<User>().FirstOrDefault(i => i.Id == 3);
exec sp_executesql N' SELECT TOP 1 [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0] WHERE [T0].[Id] = @P0',N'@P0 int',@P0=3
First,FirstOrDefault方法生成的SQL一致,First在没有匹配的数据时抛出异常,FirstOrDefault则返回default(T);
db.Query<User>().Single(i => i.Id == 3);
db.Query<User>().SingleOrDefault(i => i.Id == 3);
exec sp_executesql N' SELECT TOP 2 [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0] WHERE [T0].[Id] = @P0',N'@P0 int',@P0=3
与First,FirstOrDefault类似,不同之处在与返回了2条数据,用于判断是否有重复,如果有重复则抛出异常
并集:不包含重复项,生成Union关键字db.Query<User>().Union(db.Query<User>().Where(i=>i.Id<10));
exec sp_executesql N' SELECT [T0].[Id] AS C0,[T0].[Age] AS C1,[T0].[Name] AS C2,[T0].[CreateDay] AS C3,[T0].[Tel] AS C4 FROM [User] AS [T0] UNION SELECT [T1].[Id] AS C5,[T1].[Age] AS C6,[T1].[Name] AS C7,[T1].[CreateDay] AS C8,[T1].[Tel] AS C9 FROM [User] AS [T1] WHERE [T1].[Id] < @P0',N'@P0 int',@P0=10
db.Query<User>().Sum(i => i.Age);
SELECT TOP 1 Sum([T0].[Age] ) AS C5 FROM [User] AS [T0]
db.Query<User>().Count();
SELECT TOP 1 Count(1 ) AS C5 FROM [User] AS [T0]
db.Query<User>().Max(i=>i.Id);
SELECT TOP 1 Max([T0].[Id] ) AS C5 FROM [User] AS [T0]
db.Query<User>().Min(i=>i.Id);
SELECT TOP 1 Min([T0].[Id] ) AS C5 FROM [User] AS [T0]
db.Query<User>().Average(i=>i.Age);
SELECT TOP 1 CAST( AVG([T0].[Age] ) As Float ) AS C5 FROM [User] AS [T0]
子查询必须以Sum,Max,Min,Average,Count,First/OrDefault,Single/OrDefault,ToList,AsEnumerable结尾,不能返回IQuery接口
聚合子查询不会生成两条查询语句,会合并到外层查询中进行查询,可以重用实体创建委托 //在子查询中如果是聚合类/All/Any的查询,则合并到外层查询中
db.Query<User>()
.Where(i => i.Id < 10)
.Select(i => new
{
UserId = i.Id,
MaxUserId = db.Query<User>().Where(m => m.Id == i.Id).Max(m => m.Id)
});
exec sp_executesql N' SELECT [T0].[Id],[T0].[Id] AS C5,( SELECT TOP 1 Max([T1].[Id] ) AS C12 FROM [User] AS [T1] WHERE [T1].[Id] = [T0].[Id] ) AS C13 FROM [User] AS [T0] WHERE [T0].[Id] < @P0',N'@P0 int',@P0=10
db.Query<User>()
.Where(i => i.Id < 10)
.Select(i => new
{
UserId = i.Id,
Orders = db.Query<Order>().Where(o => o.UserId == i.Id).ToList(),
Orders2=db.Query<Order>().Where(o => o.UserId == i.Id).AsEnumerable()
});
--外层查询
exec sp_executesql N' SELECT [T0].[Id] AS [C9],[T0].[Id] AS [C13],[T0].[Id] AS C5 FROM [User] AS [T0] WHERE [T0].[Id] < @P0',N'@P0 int',@P0=10
--内层Orders查询
exec sp_executesql N' SELECT [T1].[Id] AS C6,[T1].[UserId] AS C7,[T1].[Goods] AS C8,[T0].[C9] FROM [Order] AS [T1] Cross JOIN ( SELECT [T0].[Id] AS [C9] FROM [User] AS [T0] WHERE [T0].[Id] < @P0 ) AS [T0] WHERE [T1].[UserId] = [T0].[C9] GROUP BY [T1].[Id],[T1].[UserId],[T1].[Goods],[T0].[C9]',N'@P0 int',@P0=10
--内层Orders2将延迟到第一次调用Orders2的GetEnumerator时触发,只查询一次
针对非聚合类的子查询则采用分多次查询的方式,在内存中过滤,ToList相对与AsEnumerable方法,在查询时的开销较多.
如果引用了外层查询的字段,在子查询中会自动关联该条件进行Corss Join过滤
在进行子查询前,尽量对外层查询进行过滤,因为在子查询前的条件过滤,子查询中会包含该条件.在子查询之进行的过滤,子查询中不会包含
这种类型的查询无法返回委托,需要多次创建,性能较低
在子查询中进行Join调用时,不能引用外部查询字段,需要提交关联或者延迟后Select调用之后
由于在内存中进行过滤子查询,会生成一个子查询返回类型的子类,因此在子查询中不能出现匿名类型及密封类型,如果没有引用外部字段,则无所谓
db.Query<User>()
.Where(i => i.Id < 100)
.Where(m => db.Query<User>().FirstOrDefault(n => n.Id == 1) != null)
.Where(m => db.Query<User>().All(i => i.Id > 0))
.Select(n => new
{
AllIdMoreThanOne = db.Query<User>().Where(mm => mm.Id == n.Id).All(nnn => nnn.Id > 0),
Orders = db.Query<Order>().Where(m => m.Id == n.Id).ToList()
}).ToList();
--Where中的FirstOrDefault!=null转换成EXISTS
--外层查询
exec sp_executesql N' SELECT [T0].[Id] AS [C20],[T0].[Id] AS [C25], (SELECT CASE WHEN NOT EXISTS( SELECT @P0 FROM [User] AS [T3] Cross JOIN ( SELECT [T0].[Id] AS [C20] FROM [User] AS [T0] WHERE [T0].[Id] < @P1 AND EXISTS ( SELECT TOP 1 @P2 FROM [User] AS [T1] WHERE [T1].[Id] = @P3) AND NOT ( EXISTS ( SELECT TOP 1 @P4 FROM [User] AS [T2] WHERE NOT ( [T2].[Id] > @P5 )) ) ) AS [T0] WHERE [T3].[Id] = [T0].[C20] AND NOT ( [T3].[Id] > @P6 ) GROUP BY [T3].[Id],[T3].[Age],[T3].[Name],[T3].[CreateDay],[T3].[Tel],[T0].[C20] ) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END ) AS C21 FROM [User] AS [T0] WHERE [T0].[Id] < @P7 AND EXISTS ( SELECT TOP 1 @P8 FROM [User] AS [T1] WHERE [T1].[Id] = @P9) AND NOT ( EXISTS ( SELECT TOP 1 @P10 FROM [User] AS [T2] WHERE NOT ( [T2].[Id] > @P11 )) )',N'@P0 bit,@P1 int,@P2 int,@P3 int,@P4 int,@P5 int,@P6 int,@P7 int,@P8 int,@P9 int,@P10 int,@P11 int',@P0=1,@P1=100,@P2=1,@P3=1,@P4=1,@P5=0,@P6=0,@P7=100,@P8=1,@P9=1,@P10=1,@P11=0
--内层All被合并到外层查询中
--内层ToList子查询
exec sp_executesql N' SELECT [T4].[Id] AS C22,[T4].[UserId] AS C23,[T4].[Goods] AS C24,[T0].[C25] FROM [Order] AS [T4] Cross JOIN ( SELECT [T0].[Id] AS [C25] FROM [User] AS [T0] WHERE [T0].[Id] < @P0 AND EXISTS ( SELECT TOP 1 @P1 FROM [User] AS [T1] WHERE [T1].[Id] = @P2) AND NOT ( EXISTS ( SELECT TOP 1 @P3 FROM [User] AS [T2] WHERE NOT ( [T2].[Id] > @P4 )) ) ) AS [T0] WHERE [T4].[Id] = [T0].[C25] GROUP BY [T4].[Id],[T4].[UserId],[T4].[Goods],[T0].[C25]',N'@P0 int,@P1 int,@P2 int,@P3 int,@P4 int',@P0=100,@P1=1,@P2=1,@P3=1,@P4=0
/// <summary>
/// int 扩展
/// </summary>
public static class IntExtension
{
[SQLFunctionHandler(typeof(IntSQLFunctionHandler))]
public static int Add(this int i, int n)
{
//如果没有常数调用(Add(1,2),1.Add(2))的情况,可以抛出异常
return i + n;
}
}
/// <summary>
/// 处理Function的SQL生成
/// </summary>
public class IntSQLFunctionHandler : ISQLFunctionHandler
{
public Type SoupportedType => typeof(int);
public bool HandleFunction(ISQLGenerator generator, MethodCallExpression methodCall)
{
//SQL 生成,调用generator可以对常量进行参数化处理
if (methodCall.Method.Name == "Add")
{
generator.Attach(methodCall.Arguments[0]);
generator.Attach(" + ");
generator.Attach(methodCall.Arguments[1]);
return true;
}
return false;
}
}
//使用
db.Query<User>().Where(i => i.Id.Add(2) < 10).Select(i => new { Id = i.Id.Add(3), Name = i.Name});
exec sp_executesql N' SELECT [T0].[Id] + @P0 AS C5,[T0].[Name] AS C6 FROM [User] AS [T0] WHERE [T0].[Id] + @P1 < @P2',N'@P0 int,@P1 int,@P2 int',@P0=3,@P1=2,@P2=10
int,int?char,char?,double,double?,short,short?,byte,byte?,bool,bool?
decimal,decimal?,DateTime,DateTime?uint,uint?ulong,ulong?,float,float?
ushort,ushort?,string
如:
db.Query<User>().Select(i => i.Id.ToString());
SELECT CAST ([T0].[Id] AS NVARCHAR ) FROM [User] AS [T0]
IsNullOrEmpty,IsNullOrWhiteSpace
StartsWith,EndsWith
Contains,Trim
TrimStart,TrimEnd
IndexOf,Substring
ToLower,ToUpper
Replace,Concat
//Id包含1
db.Query<User>().Select(i => i.Id.ToString().Contains("1"));
exec sp_executesql N' SELECT ( CASE WHEN CAST ([T0].[Id] AS NVARCHAR ) LIKE ''%''+ @P0 +''%'' THEN @P1 ELSE @P2 END ) FROM [User] AS [T0]',N'@P0 nvarchar(1),@P1 bit,@P2 bit',@P0=N'1',@P1=1,@P2=0
Abs,Acos,Asin
Atan,Cos,Ceiling
Floor,Exp,Log10
Sign,Sin,Sqrt
Tan,Atan2,Log
Max,Min,PowRound,
SinhCosh,TanhTruncate
db.Query<User>().Select(i => Math.Max(i.Id, i.Age));
SELECT CASE WHEN [T0].[Id] > [T0].[Age] THEN [T0].[Id] ELSE [T0].[Age] END FROM [User] AS [T0]
//db.AddRange()
db.Add(new User(){Id=1,Age=18,CreateDate=DateTime.Now,Name="张三",Tel="1234567879"});
var rowsCount=db.Save();
db.Update(new User(){Id=1,Age=18,CreateDate=DateTime.Now,Name="张三",Tel="1234567879"});
var rowsCount=db.Save();
db.Delete(new User(){Id=1,Age=18,CreateDate=DateTime.Now,Name="张三",Tel="1234567879"});
var rowsCount=db.Save();
1.Update,Delete操作的类型需要包含一个名为"Id"的属性或者字段,或者在某个属性或字段上应用PrimaryAttribute/AutoIncrementAttribute特性
2.插入的类型如果包含了AutoIncrementAttribute特性的成员,则在Save之后会更新该成员的值
3.针对Add,Update,Delete操作,会合并到一条SQL语句中执行(针对第2种情形,则单独一条进行插入)
var u = db.Query<User>().FirstOrDefault(i => i.Id == 1);
var du = db.Query<User>().FirstOrDefault(i => i.Id == 10);
u.Name = "拜拜拜";
db.Add(new User() { Id = 999, Name = "张三", Age = 18, CreateDay = DateTime.Now, Tel = "12345978" });
db.Add(new User() { Id = 10001, Name = "李四", Age = 19, CreateDay = DateTime.Now, Tel = "12345978" });
db.Update(u);
db.Delete(du);
var rowsCount = db.Save();
exec sp_executesql N'DECLARE @__ROWCOUNT__ INT=0;
;INSERT INTO [User]([Id],[Age],[Name],[CreateDay],[Tel]) VALUES (@P0,@P1,@P2,@P3,@P4),(@P5,@P6,@P7,@P8,@P9);
SELECT @__ROWCOUNT__=@__ROWCOUNT__+ROWCOUNT_BIG();
;UPDATE [User]SET [Age]=@P10,[Name]=@P11,[CreateDay]=@P12,[Tel]=@P13 WHERE Id=@P14;;SELECT @__ROWCOUNT__=@__ROWCOUNT__+ROWCOUNT_BIG();
;DELETE FROM [User] WHERE Id=@P15;
;SELECT @__ROWCOUNT__=@__ROWCOUNT__+ROWCOUNT_BIG();SELECT @__ROWCOUNT__ AS ROWSCOUNT;',N'@P0 int,@P1 int,@P2 nvarchar(2),@P3 datetime,@P4 nvarchar(8),@P5 int,@P6 int,@P7 nvarchar(2),@P8 datetime,@P9 nvarchar(8),@P10 int,@P11 nvarchar(3),@P12 datetime,@P13 nvarchar(20),@P14 int,@P15 int',@P0=999,@P1=18,@P2=N'张三',@P3='2018-02-06 16:00:39.850',@P4=N'12345978',@P5=10001,@P6=19,@P7=N'李四',@P8='2018-02-06 16:00:39.853',@P9=N'12345978',@P10=2,@P11=N'拜拜拜',@P12='2017-08-13 12:30:00',@P13=N'Wheaa2 ',@P14=1,@P15=10
当没有任何字段变更时,及时调用了Update,也不会修改数据库,能减少了修改次数
//未跟踪
var u = db.Query<User>().FirstOrDefault(i => i.Id == 1);
u.Name = "拜拜拜";
db.Update(u);
db.Save();
//开启跟踪
var u = db.Query<User>().FirstOrDefault(i => i.Id == 1);
db.TrackEntity(u);
u.Name = "拜拜拜";
db.Update(u);
db.Save();
--未跟踪
exec sp_executesql N'DECLARE @__ROWCOUNT__ INT=0;
;UPDATE [User]SET [Age]=@P0,[Name]=@P1,[CreateDay]=@P2,[Tel]=@P3 WHERE Id=@P4;
;SELECT @__ROWCOUNT__=@__ROWCOUNT__+ROWCOUNT_BIG();SELECT @__ROWCOUNT__ AS ROWSCOUNT;',N'@P0 int,@P1 nvarchar(3),@P2 datetime,@P3 nvarchar(20),@P4 int',@P0=2,@P1=N'拜拜拜',@P2='2017-08-13 12:30:00',@P3=N'Wheaa2 ',@P4=1
--开启跟踪
exec sp_executesql N'DECLARE @__ROWCOUNT__ INT=0;
;UPDATE [User]SET [Name]=@P1 WHERE Id=@P4;
;SELECT @__ROWCOUNT__=@__ROWCOUNT__+ROWCOUNT_BIG();SELECT @__ROWCOUNT__ AS ROWSCOUNT;',N'@P1 nvarchar(3),@P4 int',@P1=N'拜拜拜',@P4=1
如果没有在调用Save之前手动开启事务,则在Save方法调用时,会自动开始一个事务.如果手工开启了事务,则不会
事务嵌套,慎用
var trans = db.BeginTransaction();
try
{
db.Add(new User() { Id = 9999999, Name = "张三", Age = 18, CreateDay = DateTime.Now, Tel = "12345978" });
db.Save();
trans.Commit();
}
catch
{
trans.Rollback();
}
CPU:I7-4770K
内存:8GB
硬盘:SSD
OS:Win10
Db:Microsoft SQL Server 2016 (SP1) Express
//预制数据
for (var i = 0; i < 500000; i++)
{
db.Add(new User()
{
Id = i,
Age = i,
Name = "Name" + i,
CreateDay = DateTime.Now,
Tel = "123456789"
});
}
db.Save();
50万条数据ToList,重复五次,每次条件不一样,取平均值
var st = new Stopwatch();
//预热
db.Query<User>().Where(i => i.Id > 0).ToList();
st.Start();
for (var j = 0; j < 5; j++)
{
db.Query<User>().Where(m => m.Id > j).ToList();
}
st.Stop();
Console.WriteLine("50万条ToList:" + st.ElapsedMilliseconds / 5.0);
//50万条ToList:1399.2毫秒
前40万条数据ToList,重复五次,每次条件不一样,取平均值
var st = new Stopwatch();
//预热
db.Query<User>().Where(m => m.Id > 0).Take(1).ToList();
st.Start();
for (var j = 0; j < 5; j++)
{
db.Query<User>().Where(m => m.Id > j).Take(400000).ToList();
}
st.Stop();
Console.WriteLine("前40万条ToList:" + st.ElapsedMilliseconds / 5.0);
//前40万条ToList:1166.6毫秒
循环查询2万次,每次查询一条数据,数据不重复
var st = new Stopwatch();
//预热
db.Query<User>().Where(i => i.Id == 0).ToList();
st.Start();
for (var j = 0; j < 5; j++)
{
for (var i = 0; i < 20000; i++)
{
db.Query<User>().Where(m => m.Id == i).ToList();
}
}
st.Stop();
Console.WriteLine("循环查询2万次,每次查询一条数据:" + st.ElapsedMilliseconds / 5.0);
//循环查询2万次,每次查询一条数据:10519毫秒