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 为参数化
在配置为自动替换 SQL 拼接参数 (CRL.SettingConfig.ReplaceSqlParameter=true), 实际输出将为:
- 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;
- }
异步插入 MSMQ 的实现
- select top 10 Id,ProductId,ProductName1 from ProductData a where a.addtime>=@p1 and a.id=@p2
在某个业务中, 一张表很频繁的单个插入, 占用大量资源, 专门为这写个消息队列好像不怎么高明, 下次又有这样的情况怎么办
于是有就了, 为每个对象定义自已的消息队列和处理, 用第三方的不太好集成, 就是微软自家的吧
- /// <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;
欢迎下载源码交流讨论
来源: https://www.cnblogs.com/hubro/p/8126412.html