“针对即席工作负荷进行优化”如何影响你的计划缓存 - Woodytu - 博客园

mikel阅读(614)

来源: “针对即席工作负荷进行优化”如何影响你的计划缓存 – Woodytu – 博客园

今天我想谈下SQL Server里的针对即席工作负荷进行优化(Optimize for Adhoc Workload)服务器配置选项,还有它如何影响你的计划缓存。在我们挖掘细节之前,我想向你展示下SQL Server如何重用缓存的执行计划。

执行计划重用

每次你提交一个查询给SQL Server,SQL Server检查计划缓存来看看是否有现存的缓存计划可供重用。SQL Server对提交的SQL语句计算哈希值,并用这个哈希值在计划缓存里检查现存的执行计划(实际或更复杂,因为其他的选项——例如SET选项——也会影响执行计划重用)。

如果缓存的计划找到,执行计划会重用。不然的话新的执行计划会通过查询优化器编译,最后放入计划作为后期重用。现在假设有下列写的很烂的C#应用程序。

复制代码
1 for (int i = 1; i <= 10000; i++)
2 {
3    cmd = new SqlCommand(
4    "SELECT * FROM Sales.SalesOrderDetail WHERE SalesOrderDetailID = " + i.ToString(), cnn);
5    SqlDataReader reader = cmd.ExecuteReader();
6    reader.Close();
7 }
复制代码

这个程序在loop循环里调用了1000次SELECT语句——使用硬编码参数值。使用硬编码参数值,SQL语句的哈希值总会不一样,因此SQL Server不能重用缓存的计划。作为副作用,你在计划缓存里存储了1000个不同的执行计划。

每个执行计划也会占用一些内存(在这里是16KB),因此你浪费了近160M的计划缓存!假设现在你不能修改你程序的实现方法,你还是要提高SQL Server里计划缓存的内存占用。这就要用到“针对即席工作负荷进行优化”服务器配置选项。

针对即席工作负荷进行优化(Optimize for Adhoc Workload)

这个服务器配置选项自SQL Server 2008后就引入了,它用来改变SQL Server缓存和重用执行计划方式。一旦你启用这个配置选项,SQL Server并不再真正缓存你的整个执行计划。SQL Server只存储所谓的编译计划存根(Compiled Plan Stub)。在SQL Server 2014上,对你的SQL语句,存根值是352 bytes的哈希值。

这样的话对于10000个提交的SQL语句现在我们在计划缓存里只需要7MB的内存。这和刚才的160MB相比已经是巨大的区别!使用这个存根值,SQL Server现在能记住先前执行的特定SQL语句。

现在当你再次执行同一SQL语句是,SQL Server在执行计划里找存根值,再次编译你的执行计划,最后在计划缓存里保存完整执行计划结果。因此当SQL语句被执行至少2次时,这个执行计划才会被缓存。SQL语句只执行一次的话(即席SQL语句),在计划缓存里只需要352 bytes。

因此使用这个配置选项你可以减少计划缓存的内存占用——在你的程序里不需要任何修改!可以看下计划缓存文章来了解更多。

小结

在这篇文章里我向你展示了一个非常简单的方法来处理对于写得很糟的应用程序,它用不同的不能重用的执行计划污染你的计划缓存。“针对即席工作负荷进行优化”服务器配置选项也是我经常建议默认启用的。它会他来计划缓存更好的内存管理。

副作用是你引入的轻量的CPU负担,因为每个执行计划必须编译2次才会存储在计划缓存里。当一般来说对于这个额外编译,你应该有足够的CPU可用空间。

感谢关注!

参考文章:

https://www.sqlpassion.at/archive/2015/07/13/how-optimize-for-adhoc-workload-impacts-your-plan-cache/

注:此文章为WoodyTu学习MS SQL技术,收集整理相关文档撰写,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

PowerDesigner(PowerDesigner15.1.0.2850)下载、安装以及破解 - Fonkie - 博客园

mikel阅读(674)

来源: PowerDesigner(PowerDesigner15.1.0.2850)下载、安装以及破解 – Fonkie – 博客园

一、先安装PowerDesigner15(PowerDesigner15.1.0.2850),下载地址如下:

http://download.sybase.com/eval/PowerDesigner/PowerDesigner15_Evaluation.exe

二、破解文件下载地址:

http://download.csdn.net/source/1605189

将下载的破解文件pdflm15.dll覆盖到PowerDesigner15安装目录下(如:C:\Program Files\Sybase\PowerDesigner 15)

SQL server 导入数据提示“未在本地计算机上注册Mircrosoft.ACE.OLEDB.12.0 提供程序”的解决办法_天道十二的博客-CSDN博客_sql server 未在本地计算机上注册“mi oledb 12

mikel阅读(1606)

来源: SQL server 导入数据提示“未在本地计算机上注册Mircrosoft.ACE.OLEDB.12.0 提供程序”的解决办法_天道十二的博客-CSDN博客_sql server 未在本地计算机上注册“mi oledb 12

在用SQL server数据库 导入本地文件数据的时候出现提示信息“未在本地计算机上注册Mircrosoft.ACE.OLEDB.12.0 提供程序”,

这种提示是由于需要安装:Microsoft Office Access Database Engine 2007

下载链接是:

http://download.microsoft.com/download/7/0/3/703ffbcb-dc0c-4e19-b0da-1463960fdcdb/AccessDatabaseEngine.exe

下载完以后安装

这是在导入就没有任何问题了;

————————————————
版权声明:本文为CSDN博主「天道十二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38842357/article/details/73538579

microsoft.ACE.oledb.12.0异常解决(亲测有效)_weixin_44668267的博客-CSDN博客_microsoft.ace.oledb.12.0

mikel阅读(476)

来源: microsoft.ACE.oledb.12.0异常解决(亲测有效)_weixin_44668267的博客-CSDN博客_microsoft.ace.oledb.12.0

报错:
正文:废话不多说,我在网上找了一大批的教程,都在说要下载一个微软的2007的控件,但是到链接指向的时候要么不能下载,要是链接页面404,要么就是下载的文件安装之后没什么卵用!
附带链接百度云–wwyu
链接: https://pan.baidu.com/s/1D7wB6ZRG_wm4_qkGPMnlMg 提取码: wwyu 复制这段内容后打开百度网盘手机App,操作更方便哦
提取码wwyu
————————————————
版权声明:本文为CSDN博主「GLionII」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44668267/article/details/91489903

12个Visual Studio调试效率技巧 - 芝麻麻雀 - 博客园

mikel阅读(521)

来源: 12个Visual Studio调试效率技巧 – 芝麻麻雀 – 博客园

在这篇文章中,我们假定读者了解VS基本的调试知识,如:

  • F5 开始使用调试器运行程序
  • F9 在当前行设置断点
  • F10 运行到下一个断点处
  • F5 从被调试的已停止程序恢复执行
  • F11 步进到函数内(如果当前程序指针指向一个函数)
  • F10 步过函数(如果当前程序指针指向一个函数)
  • Shift+F11 步出执行的函数
  • 暂停执行
  • 附加到进程
  • 鼠标悬停时快速查看源代码中的元素
  • 调试窗口:局部变量、监视、即时窗口、模块、调用堆栈、异常设置

许多开发人员使用这个功能强大的工具包来处理调试会话。然而,Visual Studio调试工具提供了更多的功能。下面是一系列Visual Studio调试效率技巧。注意,这些提示和快捷方式已经在的Visual studio 2019 16.6 EN-US版本中进行了验证,验证时Visual studio没有安装扩展。

1、运行到光标位置

使用快捷键Ctrl+F10,您可以让调试器运行到光标所在行位置。

运行到光标位置

2、通过点击鼠标,运行到当前位置

在调试运行的程序时,通过鼠标悬停在当前行的代码上时,出现绿色的符号,可以点击此符号,直接让断点运行到此处。
通过点击鼠标,运行到当前位置

3、在此处作为下一条要执行的语句

在调试运行的程序时,通过鼠标悬停在当前行的代码上时,通过按住Ctrl键转换为将此处作为下一条要执行的语句。它与通过绿色箭头符号运行到这里不同,此功能将会跳过中间的语句,直接将断点跳转到此处。因此,在下面的动图中,我们可以在监视窗口中引用obj仍然为null,中间的MyClass构造函数并没有被执行。
在此处作为下一条要执行的语句

4、数据断点:当值发生变化时,触发中断(值更改时中断)

当你设置一个非静态的设置器为断点时,当所有对象的属性的值发生更改时触发断点。通过局部窗口(监视器窗口)右键点击:值更改时中断菜单,单个对象也可以获得相同的行为。

下面的动画说明了这个功能,只有当obj2.Prop发生变化时,命中断点,而obj1.Prop发生变化时没有命中断点。

注意:数据断点绑定到活动对象时,旨在调试期间起作用。因此,一旦调试过程停止,设置的断点就会丢失,在以后的调试过程中不能重用它。

数据断点:值更改时中断

5、条件断点

可以将条件附加到断点中,以便尽在特定场景中触发中断。在下面的动图中,我们在循环中定义条件i>6的断点。然后点击继续,可以看到一旦断点停止,i的值实际上变成了7

6、跟踪断点

在遇到断点时,停止程序执行时最常见的操作。但是,你可以选择在输出窗口中不终止(或带终止)打印一些跟踪信息。下面的动图说明了这种可能性。我们在输出窗口中跟踪i从0到9的值。注意:跟踪断点在编辑器的断点显示位置显示为菱形形状。

注意,条件和跟踪操作都可以在断点上指定。
跟踪断点

7、跟踪超出作用域的对象

在监视窗口中,通过当前执行范文内引用的名称来跟踪对象。但是,当这样的跟踪引用超出作用域时,即使在引用对象仍处于活动状态时,它在监视窗口的上下文也不安的毫无意义并且被禁用。

在许多情况下,我们想继续跟踪作用域外对象的状态。为此,请在监视窗口中右键单击此类引用,单击菜单[Make Object ID] 创建对象ID(M),并要在监视器中添加$1(或者$2,$3,…,取决于你已经创建了多个对象ID)。

下面的动图演示了如何跟踪作用域外对象的属性获取器的状态,该属性获取器以字符串的形式返回实际的日期时间。它很好地显示了当引用objFct()上下文中超出作用域时,要观看的obj项将被禁用,而$1仍然会获得更新。
跟踪超出作用域的对象

8、查看函数返回的值

函数返回的值有时在源代码中被忽略,或者有时这个值在调试时无法被显示的访问。

这样的返回值可以显示在调试->窗口->自动窗口中。伪变量$ReturnValue也可以在即时窗口和监视窗口中使用,以方便查看最后一个函数调用的返回值。

注意,菜单调试->窗口->自动窗口仅在Visual Studio调试器附加到进程并且程序被调试器暂停时可用。

查看函数返回的值

9、重新附加到进程

Visual Studio 2017开始,重新附加到进程Shift+Alt+P工具被提出,并且非常方便。将调试器附加到某个进程后,Visual Studio会记住它,并建议将调试器重新附加到同一进程。斜体也一样,因为这里有一个关于进程标识的启发式方法:

  • 如果已附加的进程仍然运行着,重新附加到进程,重新附加到它。
  • 否则,Visual Studio将尝试查找和前一个进程名具有相同名称的单进程,并将调试器重新附加到该进程。
  • 如果找到几个使用此名称的进程,则打开“附加到进程”对话框,只显示名称相同的进程
  • 如果找不到具有此名称的进程,则显示“附加到进程”对话框

重新附加到进程

重新附加到进程也适用于涉及多个进程的调试会话。在这种情况下,Visual Studio会尝试使用上述相同的启发式方法来查找它附加到的所有进程。

10、在即时窗口和在观察窗口的No-Side-Effect评估

有时,在即时窗口或监视窗口中评估表达式时,某些状态会更改。这种行为通常时不希望发生的。你不想仅仅因为需要评估表达式的值而破坏调试程序的状态。这种情况被称为Heisenbug,该术语时物理学家Werner Heisenberg的双关语,它首先断言了量子力学的观察者效应,该现象指出,观察系统的行为不可避免的会改变器状态。

为了避免更改任何状态,你可以在表达式后面加上nse(No-Side-Effect)。下面的动图说明了这种可能性(在监视窗口中监视State的值是否有变化)。

在即时窗口和在观察窗口的评估

下面这种动图是nse在监视窗口的使用。由于SideEffectFct()所观察的项中有Refresh评估按钮,所以此示例比前一个示例更简单。

11、在源码中显示线程

调试多线程应用程序是有名的复杂。希望在源码中显示线程按钮能提供很大的帮助。它在编辑器的左侧边栏引入标记图标,以跟踪其他线程被暂停的位置。这个标记可以用来显示线程ID,并最终切换到另一个线程。注意:如果至少两个线程在同一位置暂停,则会显示不同的标记符号。
在源码中显示线程

更多调试多线程应用程序的技巧可以在这个微软文档中找到:Get started debugging multithreaded applications (C#, Visual Basic, C++)

https://docs.microsoft.com/en-us/visualstudio/debugger/get-started-debugging-multithreaded-apps?view=vs-2019

下面是这个演示的源代码,如果你想演示它,可以进行参考:

using System;
using System.Threading;
 
class Program {
   static void Main() {
      for (int i=0; i< 5; i++) {
         // Avoid capturing a loop variable in the lambda below
         int j = i;
         // So 2 thread are blocked on '0' case
         if (j == 1) { j = 0; } 
         ThreadPool.QueueUserWorkItem(delegate { Method(j); });
      }
      Thread.Sleep(60000);
   }
   static void Method(int id) {
      switch(id) {
         case 0:
            Thread.Sleep(60000); break;
         case 1:
            Thread.Sleep(60000); break;
         case 2:
            Thread.Sleep(60000); break;
         case 3:
            Thread.Sleep(60000); break;
         case 4:
            Thread.Sleep(60000); break;
      }
   }
}

12、从反编译的IL代码中调试源代码

我们经常依赖一些黑盒组件:我们没有源代码的组件。

但是,在调试复杂行为时,观察甚至调试引用的黑盒组件引用的逻辑。这就是为什么从16.5版本开始,Visual Studio 2019可以从编译好的程序中生成一些源代码。这样的源代码是可以调试的。这个特性是基于开源软件(OSS)工程:ILSpy(https://github.com/icsharpcode/ILSpy)。

反编译菜单可以在模块窗口的组件右键菜单(如下面的动图所示)和Source Not FoundNo Symbols Loaded对话框中给出。

IL代码反编译为源代码不可能是完美的,因为一些源代码信息在编译时丢失了。因此,这个特性有一些限制,在这个官方文档的最后会解释:Generate source code from .NET assemblies while debugging

https://docs.microsoft.com/en-us/visualstudio/debugger/decompilation?view=vs-2019

从反编译的IL代码中调试源代码

结尾

Visual Studio非常出色,在调试方面尤其出色。 在这里,我试图选择一些既隐藏又经常有用的技巧,希望它们能帮助您提高生产率。

Sqlserver内存管理:限制最大占用内存_donghustone的专栏-CSDN博客_sqlserver内存限制

mikel阅读(477)

来源: Sqlserver内存管理:限制最大占用内存_donghustone的专栏-CSDN博客_sqlserver内存限制

一、SQLServer对系统内存的管理原则是:按需分配,且贪婪(用完不还)。它不会自动释放内存,因此执行结果集大的SQL语句时,数据取出后,会一直占用内存,直到占满机器内存(并不会撑满,还是有个最大限制,比机器内存稍小),在重启服务前,SQLServer不会释放该内存,也没有任何办法可人为释放。以下命令虽然可释放缓存,但SQLserver并不会因此释放已占用的内存。

DBCC FREEPROCCACHE #清除存储过程相关的缓存
DBCC FREESESSIONCACHE #清除会话缓存
DBCC FREESYSTEMCACHE(‘All’) #清除系统缓存
DBCC DROPCLEANBUFFERS #清除所有缓存
二、唯一的办法是:人为修改最大可占用物理内存进行限制。

三、修改办法如下:

管理员用户登录数据库客户端(Microsoft SQL Server Management Studio),在实例名(树形菜单根节点,显示登录名那栏)上右键,选择属性–>内存,设置“最大服务器内存”,单位为MB,可根据实际内存调整。

四、如此调整后,机器内存便不会被sqlserver耗尽了。

 

扩展:(转自网络)

由于Sql Server对于系统内存的管理策略是有多少占多少,除非系统内存不够用了(大约到剩余内存为4M左右),Sql Server才会释放一点点内存。所以很多时候,我们会发现运行Sql Server的系统内存往往居高不下。这些内存一般都是Sql Server运行时候用作缓存的,例如你运行一个select语句,那么Sql Server会将相关的数据页(Sql Server操作的数据都是以页为单位的)加载到内存中来,下一次如果再次请求此页的数据的时候,就无需读取磁盘了,大大提高了速度。这类的缓存叫做数据缓存。还有一些其他类型的缓存,如执行存储过程时,Sql Server需要先编译再运行,编译后的结果也会缓存起来,下一次就无需再次编译了。
————————————————
版权声明:本文为CSDN博主「东东不邪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/donghustone/article/details/83338185

class A where T:new() - 鷇音子 - 博客园

mikel阅读(448)

来源: class A where T:new() – 鷇音子 – 博客园

  class A<T> where T:new()

这是类型参数约束,where表名了对类型变量T的约束关系。where T:A 表示类型变量是继承于A的,或者是A本省。where T: new()指明了创建T的实例应该使用的构造函数。

.NET支持的类型参数约束有以下五种:

where T: struct                                     T必须是一个结构类型

where T: class                                       T必须是一个类(class)类型,不是结构(structure)类型

where T: new()                                      T必须要有一个无参构造函数

where T: NameOfBaseClass                     T必须继承名为NameOfBaseClass的类

where T: NameOfInterface                      T必须实现名为NameOfInterface的接口

无论走的多远,都应该时常回过头看看。之所以写这个博客,主要的原因是为了记录自己的成长痕迹和所学到的技术,以便于日后的反思。

SqlServer中使用row_number() over实现通用的分页存储过程 - daisy_thq - 博客园

mikel阅读(643)

来源: SqlServer中使用row_number() over实现通用的分页存储过程 – daisy_thq – 博客园

–通用的分页存储过程

create procedure sp_pager

(

@SQL nvarchar(4000), –要分页的SQL语句

@CurrentPageNo int,  –当前页面索引

@PageSize int,           –每一页要显示的页数

@TotalNum int output –数据的总条数 (输出参数)

)

as

declare @sqlcmd varchar(8000)

–查询数据

set @sqlcmd = ‘select * from (‘ + @Sql + ‘) a  where RowIndex between ‘ +  convert(nvarchar,(@CurrentPageNo-1) * @PageSize + 1) + ‘ and ‘ + convert(varchar,@CurrentPageNo * @PageSize)

exec(@sqlcmd)

print (@sqlCmd)

–求记录总数

create table tempTable(num int)

insert into tempTable  exec(‘select count(*) from (‘ + @Sql + ‘) a’)

select @TotalNum=(select * from tempTable)

drop table tempTable

go

–=========================================测试存储过程

declare @Sql varchar(5000)

declare @CurrentPageNo int

declare @PageSize int

declare @TotalNum int

set @CurrentPageNo = 2

set @PageSize = 4

set @Sql=’ select products.PID,products.PName,products.MarketPrice,productDispose.ShopPrice,row_number() over (order by products.PID) as RowIndex from ProductsDisposeInfo productDispose inner join ProductsInfo products on productDispose.PID=products.PID’

exec sp_pager @Sql,@CurrentPageNo,@PageSize,@TotalNum output

print @TotalNum

C# 表达式树Lambda扩展(四) - 园子的蜗牛 - 博客园

mikel阅读(511)

来源: C# 表达式树Lambda扩展(四) – 园子的蜗牛 – 博客园

一、前言

本来计算这篇文章在后面需要运用的时候写的,但是既然写到表达式的扩展呢,就一起写完吧。

看到这个标题就有一种疑问,Lambda表达式本来就是表达式树,还需要怎么扩展?那就看看下面的内容,你就知道了。

表达式系列目录

C# 表达式树讲解(一)

C# 表达式树遍历(二)

C# 表达式树分页扩展(三)

C# 表达式树Lambda扩展(四)

二、Lambda扩展

这里先不忙解答上面的问题,我们先看下这样一个应用场景。

一个页面的请求,里面带有一些条件查询,请求类如下

public class ScoreRequest
{
    public string CourseName { get; set; }
    public string StudentName { get; set; }
}

要求查询与课程名称和学生名称匹配的数据

数据源我们就以上一例子的数据源

数据源类

public class ScoreClass
{
    public string CourseName { get; set; }
    public string StudentName { get; set; }
    public decimal Score { get; set; }
}

添加数据

复制代码
var datas = new List<ScoreClass>();
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生A",
    Score = 60
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生B",
    Score = 65
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生C",
    Score = 70
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生D",
    Score = 75
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生E",
    Score = 80
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生F",
    Score = 81
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生G",
    Score = 82
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生H",
    Score = 83
});
datas.Add(new ScoreClass
{
    CourseName = "数学",
    StudentName = "学生I",
    Score = 84
});
复制代码

好了现在我们就查询数据

复制代码
var request = new ScoreRequest()
            {
                CourseName = "数",
                StudentName = "H"
            };
            var resultDatas = datas.Where(e => e.CourseName.Contains(request.CourseName) && e.StudentName.Contains(request.StudentName))
                .ToList();
复制代码

如果查询对象里面CourseName和StudentName字段都有值得话,这样写没问题。如果没值,那就最后的数据,就不准确了。

如果是直接拼凑SQL语句,我们可以用if(String.IsNullOrEmpty())来判断,但是现在判断了,怎么拼凑Lambda表达式呢?

所以就需要我们对Lambda表达式进行扩展,让他支持这种情况。那上面的问题,就不用再专门回答了吧!!!!

创建一个LambdaExtension的类,代码如下

复制代码
public static class LambdaExtension
{
    public static Expression<Func<T, bool>> True<T>() { return param => true; }
    public static Expression<Func<T, bool>> False<T>() { return param => false; }
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.AndAlso);
    }
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.OrElse);
    }
    private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
    {
        var map = first.Parameters
            .Select((f, i) => new { f, s = second.Parameters[i] })
            .ToDictionary(p => p.s, p => p.f);
        var secondBody = PFTParameterExtension.ReplaceParameters(map, second.Body);
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }

    private class PFTParameterExtension : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public PFTParameterExtension()
        {

        }

        public PFTParameterExtension(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        /// <summary>
        /// 替换参数
        /// </summary>
        /// <param name="map">The map.</param>
        /// <param name="exp">The exp.</param>
        /// <returns>Expression</returns>
        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new PFTParameterExtension(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map != null && map.Count > 0 && map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }

    }

}
复制代码

这里面私有化了一个表达式树访问器,他的作用主要是用来同步Lambda表达式里面的参数。

下面是调用方式

复制代码
            var expression = LambdaExtension.True<ScoreClass>();
            if (!string.IsNullOrWhiteSpace(request.CourseName))
                expression = expression.And(e => e.CourseName.Contains(request.CourseName));
            if (!string.IsNullOrWhiteSpace(request.StudentName))
                expression = expression.And(et => et.StudentName.Contains(request.StudentName));

            var resultDatas = datas.Where(expression.Compile())
                .ToList();
            Console.WriteLine($"查询结果:\n{string.Join("\n", resultDatas.Select(e => $"{e.StudentName} {e.CourseName} {e.Score}"))}");
复制代码

where条件里面只能带委托,而我们的expression是Lambda表达式,所以需要Compile进行委托编译。

运行结果:

image

仔细看代码,第一个条件And里面的参数是“e”,第二个条件里面的参数是et,同一个Lambda表达式里面(这里只有一个参数),参数肯定是一致的,所以在LambdaExtension类中,在合并两个Lambda表达式的时候,就需要将参数合并成一个。

经过这样的扩展,我们就可以根据我们的实际情况,拼凑好需要的表达式,得到我们想要的结果。

三、总结

表达式树方面的讲解,终于可以告一段落了。一直后没有这样的写文章,现在觉得写文章还是真的挺累的,今年中秋节的这三天,算是全部的给博客园了。不过这三天讲解的内容,基本上把后面Dapper的扩展需要用的技术都铺垫了,后面我们就继续对ORM的讲解了。其实没写一篇博文,蜗牛都会去罗列和梳理相关知识点,这也让蜗牛获益匪浅,也希望蜗牛的博客能帮助到园友,这就是所谓的“赠人玫瑰,手留余香”吧。

C# 表达式树分页扩展(三) - 园子的蜗牛 - 博客园

mikel阅读(519)

来源: C# 表达式树分页扩展(三) – 园子的蜗牛 – 博客园

一、前言

前面我们知道了表达树的基本知识,也明白了怎么遍历和修改一个表达式,这里我们就一个实际的场景来进行功能开发。

表达式系列目录

C# 表达式树讲解(一)

C# 表达式树遍历(二)

C# 表达式树分页扩展(三)

C# 表达式树Lambda扩展(四)

二、分页扩展

在实际的开发中,肯定会遇到这样的应用场景,一个数据源需要在页面上进行分页显示,并且页面上需要对该数据源有一个排名。本来分页可以在数据库层面完成,但是因为这里有一个排名功能,所谓的排名,就是需要查询出所有满足条件的数据,然后按照某个算法升序或者降序排列,然后按照进行排名。排名之后,然后根据分页信息,将当前页的数据返回给页面,当然中间还有自定义排序的因素。

怎么取数据源和怎么排序,这里先不做介绍,我们就实现对一个数据源分页的功能。

我们先定义好分页的实体对象

分页请求对象PageRequest.cs,因为在【ORM之Dapper运用】已经说明,所以这里我就只粘贴处代码

复制代码
public class PageRequest
    {
        /// <summary>
        /// 每页条数
        /// </summary>
        public int PageSize { get; set; }

        /// <summary>
        /// 当前页数
        /// </summary>
        public int PageIndex { get; set; }

        /// <summary>
        /// 排序字段
        /// </summary>
        public string SortBy { get; set; }

        /// <summary>
        /// 排序方式(desc、asc)
        /// </summary>
        public string Direction { get; set; }

        /// <summary>
        /// 获取排序sql语句
        /// </summary>
        /// <returns></returns>
        public string GetOrderBySql()
        {
            if (string.IsNullOrWhiteSpace(SortBy))
            {
                return "";
            }
            var resultSql = new StringBuilder(" ORDER BY ");
            string dir = Direction;
            if (string.IsNullOrWhiteSpace(dir))
            {
                dir = "ASC";
            }
            if (SortBy.Contains("&"))
            {
                resultSql.Append("").Append(string.Join(",", SortBy.Split('&').Select(e => $" {e} {dir}").ToArray()));
            }
            else
            {
                resultSql.Append(SortBy).Append("").Append(dir);//默认处理方式
            }
            return resultSql.ToString();
        }
    }
复制代码

分页的返回对象PageResponse.cs

复制代码
/// <summary>
    /// 通用分页返回
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PageResponse<T>
    {
        /// <summary>
        /// 总条数
        /// </summary>
        public long TotalCount { get; set; }

        /// <summary>
        /// 返回
        /// </summary>
        public List<T> Items { get; set; }

        /// <summary>
        /// 当前页
        /// </summary>
        public long PageIndex { get; set; }

        /// <summary>
        /// 每页条数
        /// </summary>
        public long PageSize { get; set; }

        /// <summary>
        /// 总页数
        /// </summary>
        public long TotalPages { get; set; }

        /// <summary>
        /// 返回筛选集合
        /// </summary>
        public Dictionary<string, List<string>> ResultFilter = new Dictionary<string, List<string>>();

    }
复制代码

对数据集分页方法的实现

复制代码
public class PFTPage
    {
        /// <summary>
        /// 对数据集分页
        /// </summary>
        /// <typeparam name="T">数据集对象</typeparam>
        /// <param name="source">数据集</param>
        /// <param name="page">分页信息</param>
        /// <returns></returns>
        public static PageResponse<T> DataPagination<T>(IQueryable<T> source, PageRequest page) where T : class, new()
        {
            var sesultData = new PageResponse<T>();
            bool isAsc = page.Direction.ToLower() == "asc" ? true : false;
            string[] _order = page.SortBy.Split('&');
            MethodCallExpression resultExp = null;
            foreach (string item in _order)
            {
                string _orderPart = item;
                _orderPart = Regex.Replace(_orderPart, @"\s+", "");
                string[] _orderArry = _orderPart.Split(' ');
                string _orderField = _orderArry[0];
                bool sort = isAsc;
                if (_orderArry.Length == 2)
                {
                    isAsc = _orderArry[1].ToUpper() == "ASC" ? true : false;
                }
                var parameter = Expression.Parameter(typeof(T), "t");
                var property = typeof(T).GetProperty(_orderField);
                var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                var orderByExp = Expression.Lambda(propertyAccess, parameter);
                resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(T), property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
            }
            var tempData = source.Provider.CreateQuery<T>(resultExp);
            sesultData.PageIndex = page.PageIndex;
            sesultData.PageSize = page.PageSize;
            sesultData.TotalCount = tempData.Count();

            sesultData.TotalPages = sesultData.TotalCount / sesultData.PageSize;
            if (sesultData.TotalCount % sesultData.PageSize > 0)
            {
                sesultData.TotalPages += 1;
            }

            sesultData.Items = tempData.Skip(page.PageSize * (page.PageIndex - 1)).Take(page.PageSize).ToList();
            return sesultData;
        }
    }
复制代码

 

为了测试,我们定义一个学生课程成绩的测试类

public class ScoreClass
{
    public string CourseName { get; set; }
    public string StudentName { get; set; }
    public decimal Score { get; set; }
}

调用代码

复制代码
var datas = new List<ScoreClass>();
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生A",
                Score = 60
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生B",
                Score = 65
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生C",
                Score = 70
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生D",
                Score = 75
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生E",
                Score = 80
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生F",
                Score = 81
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生G",
                Score = 82
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生H",
                Score = 83
            });
            datas.Add(new ScoreClass
            {
                CourseName = "数学",
                StudentName = "学生I",
                Score = 84
            });
            //按照Score降序排序取第一个(5条数据)
            var page = new PageRequest()
            {
                Direction= "desc",
                PageIndex=1,
                PageSize=5,
                SortBy= "Score"
            };
            var result = PFTPage.DataPagination(datas.AsQueryable(), page);

            Console.WriteLine($"分页结果:\n{string.Join("\n", result.Items.Select(e=>$"{e.StudentName} {e.CourseName} {e.Score}"))}");
复制代码

运行结果

image

监控一下result

1568516673(1)

返回的都是我们希望返回的数据。

分页公共方法里面,就是根据PageRequest里面的内容,动态的生成表达式树的查询,然后在对数据集使用我们生成的查询表达式树,就返回我们想到的数据集。

三、总结

实现数据分页的公共方法,在后面的遇到数据分页的时候,就会显得非常的方便。有没有感觉很好玩,那么下一篇我们在利用这些知识对Lambda表达式进行扩展。