CRL4.5版本已经稳定使用于目前的几个中型项目中,在实际使用中,也发现了不少问题,这些问题都在4.52中提交
CRL具体功能和使用请浏览 CRL快速开发框架系列教程
由于现在项目是一套业务系统,查询需求比较多,CRL带的语法解析都能满足,特殊的可手写SQL,在针对查询的优化,以下几点
对查询调用的监视
由于业务封装写得非常复杂,方法嵌套很严重,无法检查一个方法内有多少查询,需不需要优化,因此使用CallContext进行了监视,并生成报表
public ActionResult RunTime() { var str = CRL.Runtime.RunTimeService.Display(); return Content(str); }
最终如下图:
路径:表示调用方法的路径
DBCall:表示实例化的数据管理类
ALLCall:表示所有数据访问调用
表达式解析内存占用
在一次更新中,为了使解析速度更快,将表达式进行了缓存,只有参数进行了重解析,看上去是省了不少,如下所示
CRLExpression.CRLExpression BinaryExpressionHandler(Expression left, Expression right, ExpressionType expType) var key = string.Format("{0}{1}{2}{3}", __PrefixsAllKey, left, expType, right); var a = BinaryExpressionCache.TryGetValue(key, out cacheItem); if (a) { 返回缓存 }
但是Expression left.ToString()效率并不高,并且占内存,所以这个并没有什么卵用
字符串变量内存占用
因为CRL查询所有参数都需要进行参数化,因此需要对应的参数名,如以下表达式:b=>b.Id==1
参数名为:@p1,若再有参数,依次类推,然而@p1由 string.Format("{0}p{1}","@",1)生成,在对性能测试时发现,这个Format占用了很多内存
于是解决办法,还是缓存参数名,提前生成,重复使用
if (parameDic == null) { parameDic = new Dictionary<int, string>(); for (int i = 0; i <= 5000; i++) { parameDic.Add(i, __DBAdapter.GetParamName("p", i)); } } var _par = parameDic[parIndex]; AddParame(_par, par);
字段格式化内存占用
CRL查询默认是查询所有字段,所以查询为 select t1.Id,t1.Name,t1.Code.....,在没有手动选择查询字段时,这些select其实一直是一样的,通过性能监视发现,好多内存被这重复解析占用了
优化后,当是查询所有字段,从缓存里生成
if (GetPrefix(__MainType) == "t1.") { key = __MainType.ToString(); SelectFieldInfo value; var a = queryFieldCache.TryGetValue(key, out value); if (a) { if (!cacheAllFieldString) { var item = value.Clone(); item.CleanQueryFieldString(); _CurrentSelectFieldCache = item; } else { _CurrentSelectFieldCache = value; } return; } cache = true; }
因为CRL对查询作了关键字处理,包括表名,字段名,所以最终的语句为
以MSSQL为例:select t1.[Name] from [table1] t1
方法为
public override string KeyWordFormat(string value) { return string.Format("[{0}]", value); }
实际上,每个字段只用生成一次就行了,再次使用时从缓存中取,节省了不少内占用
public string FieldNameFormat(Attribute.FieldAttribute field) { if (string.IsNullOrEmpty(field.MapingNameFormat)) { field.MapingNameFormat = KeyWordFormat(field.MapingName); } return field.MapingNameFormat; }
手写语句提取参数
直接将参数拼在SQL里好像不怎么雅观,并且,容易造成语法错误和注入漏洞,如下:
string sql = "select top 10 Id,ProductId,ProductName1 from ProductData a where a.addtime>='2017-09-01' and a.id=234"; var helper = DBExtend; var list = helper.ExecDynamicList(sql);
CRL新增了方法,能重新处理手写的SQL为参数化
int parIndex = 1; /// <summary> /// 提取SQL参数 /// </summary> /// <param name="db"></param> /// <param name="sql"></param> /// <param name="manual"></param> /// <returns></returns> public virtual string ReplaceParameter(CoreHelper.DBHelper db,string sql,bool manual = false) { if (!SettingConfig.ReplaceSqlParameter && !manual) { return sql; } //return sql; var re = @"((\s|,)*)(\w+)\s*(>|<|=|!=|>=|<=)\s*('(.*?)'|([1-9]\d*.\d*|0.\d*[1-9]\d*))(\s|,|\))"; sql = sql + " "; if (!Regex.IsMatch(sql, re, RegexOptions.IgnoreCase)) { return sql; } Regex r = new Regex(re, RegexOptions.IgnoreCase); List<string> pars = new List<string>(); //int index = 1; for (var m = r.Match(sql); m.Success; m = m.NextMatch()) { var name = m.Groups[3]; var op = m.Groups[4]; var value1 = m.Groups[6]; var value2 = m.Groups[7]; var value = string.IsNullOrEmpty(value2.Value) ? value1 : value2; var p = m.Groups[1]; var p2 = m.Groups[8]; var pName = GetParamName("_p", parIndex); db.AddParam(pName, value.ToString()); sql = sql.Replace(m.ToString(), string.Format("{0}{1}{4}{2}{3} ", p, name, pName, p2, op)); parIndex += 1; } return sql; }
在配置为自动替换SQL拼接参数(CRL.SettingConfig.ReplaceSqlParameter=true),实际输出将为:
select top 10 Id,ProductId,ProductName1 from ProductData a where a.addtime>=@p1 and a.id=@p2
异步插入MSMQ的实现
在某个业务中,一张表很频繁的单个插入,占用大量资源,专门为这写个消息队列好像不怎么高明,下次又有这样的情况怎么办
于是有就了,为每个对象定义自已的消息队列和处理,用第三方的不太好集成,就是微软自家的吧
/// <summary> /// 添加一条记录[基本方法] /// 异步时,会定时执行批量插入,依赖MSMQ服务 /// </summary> /// <param name="p"></param> /// <param name="asyn">异步插入</param> public virtual void Add(TModel p, bool asyn = false)
调用参数为true时,则为TModel类型创建消息队列,并异步分批次插入到数据库中,比如在10秒内连续调用了100次此方法
只是将数据存入了消息队列,在队列下个处理周期,将这100条批量插入到数据库,效率倍增
DbSet方式的实现
在Entity Framework里,有DbSet的概念,配置好数据关系后,关联对象直接就能取到了
在CRL里,简单实现一下
public class Order : CRL.IModelBase
{
public CRL.Set.DbSet<ProductData> Products//返回关联的Product { get { return GetDbSet<ProductData>(b => b.Id, ProductId); } } public CRL.Set.EntityRelation<Member> Member//返回关联的Member { get { return GetEntityRelation<Member>(b => b.Id, UserId); } }
}
调用如下:
var order = new Code.Order(); //所有 var product = order.Products.ToList(); //返回关联过的查询,使用完整查询满足更多需求 var product2 = order.Products.GetQuery(); var p = new Code.ProductData() { BarCode = "33333" }; //添加一项 order.Products.Add(p); order.Products.Delete(p);//删除一项 //返回完整的BaseProvider var provider = order.Products.GetProvider(); //返回关联的member,在调用时返回,在循环内调用会多次调用数据库 var member = order.Member.Value;
十年磨一剑,在代码写得越来越深入,再回头看自已的代码,残破不堪.
欢迎下载源码交流讨论