实现自己的Linq to Sql
之前写过一篇《统一的仓储接口》,为了方便使用不同的仓储。在我们的项目中使用的是EF4.0,但是这个版本的EF有一些性能问题没有解决,又不想升级到EF6,具体EF6有没有解决暂时不清楚。我们的项目之前运行的都不错,突然一天数据库服务器CPU 100%,IIS服务器CPU又正常,过几个小时之后又恢复正常,每个星期一早上都这样,可以肯定就是用户同时操作并发过多造成,查找之后,是一个表的数据被锁住。报错was deadlocked on lock,解决办法就是查询sql上加上 with nolock,但是EF不支持,但有不想放弃linq to sql的优势,无奈只能自己实现,方案是之前已有功能并发不多的地方保持不变,依然使用EF,在并发多的地方使用自己实现的linq to sql。
目前并没有完整实现linq to sql,只是实现了单表的情况,对于有关联引用由于时间限制并未实现。
实现仓储,需要实现两个对象,[IObjectContext、IOrderedQueryable]
上篇说到的上下文对象IObjectContext
public interface IObjectContext:IDisposable
{
string Name { get; set; }
IDbConnection CreateConnection();
bool SaveChanges();
}
考虑到项目中有些特殊情况需要使用sql,所以增加了CreateConnection方法返回一个IDbConnection,当然在逼不得已的情况下使用。
DbContext的实现比较简单,重点管理一下连接池,配置,大概如下,具体的实现由于篇幅省略,源码会在篇尾附上。
public class DbContext : IDisposable, IObjectContext
{
public EntityStateManager Manager { get; private set; }
public string DbType { get; private set; }
public string ConnectionString { get; set; }
public int PoolSize { get; set; }
public bool AllowUpdateWithNoExp { get; set; }
public bool AllowDeleteWithNoExp { get; set; } List<System.Data.IDbConnection> connPool = new List<System.Data.IDbConnection>(); public DbContext(); public DbContext(string connectionString, string dbType); public DbContext(string connectionString, string dbType, bool allowUpdateWithNoExp, bool allowDeleteWithNoExp); void InitContext(string connectionString, string dbType, int poolSize, bool allowUpdateWithNoExp, bool allowDeleteWithNoExp); public EntitySet<T> GetEntitySet<T>(string tableName = null, bool noLock = false, bool noTracking = false); public int SubmitChange(); void IDisposable.Dispose(); string IObjectContext.Name { get; set; } bool IObjectContext.SaveChanges(); public System.Data.IDbConnection CreateConnection(); System.Data.IDbConnection IObjectContext.CreateConnection();
另一个对象则是仓储EntitySet<T>,大概代码如下
public class EntitySet<T> : IOrderedQueryable<T>
{
public DbContext Context { get; private set; }
string TableName { get; set; }
bool NoLock { get; set; }
bool NoTracking { get; set; } DbQueryProvider MyProvider { get; set; }
Expression MyExpression { get; set; } public EntitySet(string tableName, bool noLock, bool noTracking, DbContext context); public EntitySet(string tableName, bool noLock, bool noTracking, Expression expression, DbContext context); void InitEntitySet(string tableName, bool noLock, bool noTracking, Expression expression, DbContext context); public Expression Expression
{
get { return this.MyExpression; }
} public Type ElementType
{
get { return typeof(T); }
} public IQueryProvider Provider
{
get { return this.MyProvider; }
} public IEnumerator<T> GetEnumerator(); IEnumerator IEnumerable.GetEnumerator(); void MyProvider_OnExecuted(object sender, EventArgs e); void TrackEntity(object result); void notifyT_PropertyChanged(object sender, PropertyChangedEventArgs e); public void Add(T item, bool INSERT_IDENTITY = false); public void Update(Expression<Func<T, bool>> exp, object obj); public void Remove(T item); public void Remove(Expression<Func<T, bool>> exp);
在仓储里面我增加了我需要的东西,比如Add可以插入标识,Update可以根据表达式更新对象,而不需要把所需要的对象先取出来,修改再保存,数据量大的时候EF性能有问题,Remove也同样如此,为了防止误操作,所以在之前DbContext中增加了配置,是否允许无条件删除、更新数据。另外一个重点也就是增加nolock支持,当然这个在生成sql的时候加上就行,非常简单。
说到这里其实只不过是个大概,这里面的操作无非就是四种CRUD。我设计了四个Command来解决InsertCommand、UpdateCommand、SelectCommand、DeleteCommand,他们都继承EntityCommand只要实现一个方法public abstract int Execute(IDbConnection conn,IDbTransaction tran);
到这里其实InsertCommand、UpdateCommand、DeleteCommand实现都非常简单,因为有了实现SelectCommand的基础代码,解析Expression就简单的多了,可以说解析Expression才是整个的关键。
解析的代码没有那么多复杂的东西,我个人的原则就是尽量简单,不为了追求设计而增加多余的东西,虽然我对设计模式也很痴迷。
上图就是解析的全部代码,这其中代码其实并不重要,重要的是解决的思路,这里面有两个重要的对象QueryExpressionClosure(查询表达式闭包)、QueryCommnClosure(查询通用闭包)。
我们知道IQueryable其实就是expression tree,我记得以前很早的时候有人实现解析表达式的时候,是一边解析一边生产sql,这样的做法非常不科学,会造成很多不必要的sql闭包,
一个简单的查询:比如q.Where(c=>c.Age>20).OrderBy(c=>c.Id);就会生成最少三个闭包大概sql是这样 select xxx from (select * from (select xxx from tablex) as t1 where t1.Age>20) as t2 order by t2.Id。但是正常来说应该是select xxx from tablex where Age>20 order by Id。
所以正确的应该是先将表达式树解析成表达式闭包,在将表达式闭包解析成sql。
如何将表达式解析成表达式闭包?
稍微分析一下就可以看出,当遇到take、SKIP、sum、max、min、average、any、contains、distinct、first、firstordefault、longcount、count的时候就是另外一个sql闭包了,
大概代码如下:
protected override Expression VisitMethodCall(MethodCallExpression m)
{
var methodName = m.Method.Name.ToLower();
if (this.CurrQueryExpressionClosure == null && this.QueryExpressionClosures.Count == 0 && Utility.ToListMethods.Contains(methodName))
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = "tolist";
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
} switch (methodName)
{
case "orderbydescending":
case "orderby":
case "thenby":
case "thenbydescending":
{
if (!Utility.IgnoreOrderByMethods.Contains(this.CurrQueryExpressionClosure.MethodName))
{
this.CurrQueryExpressionClosure.OrderByExpressions.Add(m);
}
break;
}
case "groupby":
{
this.CurrQueryExpressionClosure.GroupByExpressions.Add(m.Arguments[1]);
break;
}
case "where":
{
var l = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression);
this.CurrQueryExpressionClosure.WhereExpressions.Add(l);
break;
}
case "take":
{
if (this.CurrQueryExpressionClosure.MethodName != "skip")
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
this.CurrQueryExpressionClosure.MethodName = methodName;
}
this.CurrQueryExpressionClosure.Take = System.Convert.ToInt32((m.Arguments[1] as ConstantExpression).Value);
break;
}
case "skip":
{
if (this.CurrQueryExpressionClosure.MethodName != "take")
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
this.CurrQueryExpressionClosure.MethodName = methodName;
}
this.CurrQueryExpressionClosure.Skip += System.Convert.ToInt32((m.Arguments[1] as ConstantExpression).Value);
break;
}
case "sum":
case "max":
case "min":
case "average":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
if (m.Arguments.Count > 1)
{
this.CurrQueryExpressionClosure.EvalNumericExpression = m.Arguments[1];
}
break;
}
case "any":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
if (m.Arguments.Count > 1)
{
var l = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression);
this.CurrQueryExpressionClosure.WhereExpressions.Add(l);
}
break;
}
case "contains":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
if (m.Arguments.Count > 1)
{
this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]);
}
break;
}
case "distinct":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
break;
}
case "select":
{
var lambdaExp = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression);
this.CurrQueryExpressionClosure.QuerySelectExpressions.Add(lambdaExp);
break;
}
case "first":
case "firstordefault":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.CurrQueryExpressionClosure.Take = 1;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
if (m.Arguments.Count > 1)
{
this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]);
}
break;
}
case "longcount":
case "count":
{
this.CurrQueryExpressionClosure = new QueryExpressionClosure();
this.CurrQueryExpressionClosure.MethodName = methodName;
this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure);
if (m.Arguments.Count > 1)
{
this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]);
}
break;
}
}
return base.VisitMethodCall(m);
}
有了表达式闭包之后,这个时候理解起来就清晰多了,就可以通过一个ParserContext梳理一遍表达式闭包,生成一个通用闭包,并且得到需要的信息。
通用闭包大概:
public class QueryCommnClosure
{
public int Take { get; set; }
public int Skip { get; set; }
public bool NoLock { get; set; }
public string MethodName { get; set; }
public string TableName { get; set; }
public List<string> SelectColumns { get; set; }
//public List<SelectTypeConstructor> SelectTypes { get; set; }
public Dictionary<string, string> OrderBys { get; set; }
public List<string> GroupBys { get; set; }
public List<string> WhereSQLs { get; set; }
public string EvalNumericSQL { get; set; }
public string TableAlias { get; set; } public QueryCommnClosure()
{
this.SelectColumns = new List<string>();
//this.SelectTypes = new List<SelectTypeConstructor>();
this.OrderBys = new Dictionary<string, string>();
this.GroupBys = new List<string>();
this.WhereSQLs = new List<string>();
} public void Generate(ParserContext context)
{
...篇幅限制省略
}
}
另外一个元数据,其实这个非常简单,我为了灵活,支持解析EF的edmx(msl、csdl)、Attribute(松散灵活的,实体上可以加Attribute,也可以不加)两种。有了这个元数据就可以做到实体、表的映射。
人快30了,成家却未能立业,做了一年多的项目因为省领导政策的原因失败,说实话干这个行当不知道对不对,可能是有着一张不老的脸,在别人眼里,都以为是才24、5岁,对我也是不够信任,但是实际干起来别人才知道我实力如何,但老板不知道。总是干的最多,拿的只能算个一般,呵呵...。
昆明有看上俺的可以联系下我,求出路,目前公司也不是说要倒闭什么的,其实也很稳定,但是这个项目完完了,另一个稳定gps是其他人做的,感觉在公司已经多余了,工资也不是看涨的样子,毕竟要买房,养家糊口。
实现自己的Linq to Sql的更多相关文章
- LINQ to SQL语句(7)之Exists/In/Any/All/Contains
适用场景:用于判断集合中元素,进一步缩小范围. Any 说明:用于判断集合中是否有元素满足某一条件:不延迟.(若条件为空,则集合只要不为空就返回True,否则为False).有2种形式,分别为简单形式 ...
- 年终巨献 史上最全 ——LINQ to SQL语句
LINQ to SQL语句(1)之Where 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句.Where操 ...
- LINQ to SQL语句(20)之存储过程
在我们编写程序中,往往需要一些存储过程,在LINQ to SQL中怎么使用呢?也许比原来的更简单些.下面我们以NORTHWND.MDF数据库中自带的几个存储过程来理解一下. 1.标量返回 在数据库中, ...
- LINQ to SQL语句(19)之ADO.NET与LINQ to SQL
它基于由 ADO.NET 提供程序模型提供的服务.因此,我们可以将 LINQ to SQL 代码与现有的 ADO.Net 应用程序混合在一起,将当前 ADO.NET 解决方案迁移到 LINQ to S ...
- LINQ to SQL语句(18)之运算符转换
运算符转换 1.AsEnumerable:将类型转换为泛型 IEnumerable 使用 AsEnumerable<TSource> 可返回类型化为泛型 IEnumerable 的参数.在 ...
- LINQ to SQL语句(17)之对象加载
对象加载 延迟加载 在查询某对象时,实际上你只查询该对象.不会同时自动获取这个对象.这就是延迟加载. 例如,您可能需要查看客户数据和订单数据.你最初不一定需要检索与每个客户有关的所有订单数据.其优点是 ...
- LINQ to SQL语句(14)之Null语义和DateTime
Null语义 说明:下面第一个例子说明查询ReportsToEmployee为null的雇员.第二个例子使用Nullable<T>.HasValue查询雇员,其结果与第一个例子相同.在第三 ...
- LINQ to SQL语句(10)之Insert
1.简单形式 说明:new一个对象,使用InsertOnSubmit方法将其加入到对应的集合中,使用SubmitChanges()提交到数据库. var newCustomer = new Custo ...
- Linq to SQL 语法查询(链接查询,子查询 & in操作 & join,分组统计等)
Linq to SQL 语法查询(链接查询,子查询 & in操作 & join,分组统计等) 子查询 描述:查询订单数超过5的顾客信息 查询句法: var 子查询 = from c i ...
- LinQ to SQL用法详解
LinQ是指集成化查询语言,通过映射将数据库内的表名变为C#的类名,将列名作为属性名,将表的关系作为类的成员对象.O--M--R O-Object对象(李昌辉)R-Relation关系M-Mappin ...
随机推荐
- php 字符串负值判断
2014年9月9日 11:54:54 $a = '-1'; $b = (int)$a; $c = is_numeric($a); if ($a) { echo 1; //echo 1 } else { ...
- 对Java中字符串的进一步理解
字符串在程序开发中无处不在,也是用户交互所涉及到最频繁的数据类型,那么字符串不仅仅就是我们简单的理解的String str = "abc";一起来更加深入的看一下 在Java中,字 ...
- @SuppressWarnings注解
简介:java.lang.SuppressWarnings是J2SE 5.0中标准的Annotation之一.可以标注在类.字段.方法.参数.构造方法,以及局部变量上.作用:告诉编译器忽略指定的警告, ...
- [Android UI] ProgressBar自定义
转载自:http://gundumw100.iteye.com/blog/1289348 1: 在JAVA代码中 在java代码中 ProgressBar 继承自View, 在android ...
- July 16th, Week 29th Saturday, 2016
A long road tests a horse's strength and a long task proves a man's heart. 路遥知马力,日久见人心. Do you have ...
- icon font字体图标字库汇总
最近在研究icon font图标字库,找了一些比较好的在线字库.大都是开源的,而且各有特色! 阿里icon font字库 http://www.iconfont.cn/ 这个是阿里妈妈M2UX的一个i ...
- 一箭双雕打开Genesis
打开记事本,将如下内容填入,保存时将后缀名改为bat @ ECHO 正在清理垃圾文件...del C:\tmp\*.* /f /q@ ECHO 清理完毕@ ECHO OFF@ ECHO.@ ECHO. ...
- CSS3 background-size 属性值:cover
当设置值为cover,可以呈现出图片铺满浏览器内容的视觉效果 实例 规定背景图像的尺寸: div { background:url(img_flwr.gif); background-size:80p ...
- ORACLE用SYS登录报ORA-28009:connection as SYS should be as SYSDBA OR SYSOPER解决方法
情况一:使用sqlplus登录 正常输入用户名的口令,就会报错,因为SYS是在数据库之外的超级管理员,所以我们在登录的时候 要在输入口令:口令+as sysdba(比如:123456 as sysdb ...
- 浅析十三种常用的数据挖掘的技术&五个免费开源的数据挖掘软件
一.前 沿 数据挖掘就是从大量的.不完全的.有噪声的.模糊的.随机的数据中,提取隐含在其中的.人们事先不知道的但又是潜在有用的信息和知识的过程.数据挖掘的任务是从数据集中发现模式,可以发现的模式有很多 ...