EntityFramework之Log(五)
关于日志
属性日志
DbContext.Database.Log 属性被设置为一个委托,该委托能接受带有一个字符串参数的任何方法,最主要的是,通过设置它到 TextWriter 的 Write 方法将能应用于任何的TextWriter,通过上下文自动生成的所有SQL语句将被记录到Writer中。
例如,如下代码将记录SQL在控制台上:
using (var ctx = new EntityDbContext())
{
ctx.Database.Log = Console.WriteLine;
}
【注意】上下文中的日志被设置到 Console.WriteLine ;则其所有SQL代码都将会输出在控制台上。
下面我们进行一些简单的查询、修改利用日志属性来演示在控制台上进行输出(依然利用前一篇文章所给出个三给类,如若不知其关系,请参考前一篇文章):
using (var ctx = new EntityDbContext())
{
ctx.Database.Log = Console.WriteLine;
或者
ctx.Database.log = s => Console.WriteLine(s);
var stu = ctx.Set<Student>().First(p => p.Name == "xpy0928");
stu.Grades.First().Fraction = ;
stu.Grades.Add(new Grade() { Fraction = });
ctx.SaveChangesAsync().Wait();
}
则在控制台打印如下SQL代码:
日志记录内容
(1)各种SQL命令
查询语句。例如:Linq查询、原始SQL查询
作为SaveChanges的一部分的插入(inserted)、修改(update)以及删除(delete)
由延迟加载自动生成的关联查询
(2)参数
(3)是否正在被异步执行的命令
(4)当命令开始执行时,时间戳的显示
(5)无论命令是否被成功完成,还是通过抛出异常而失败或者是通过异步被取消
(6)一些显示结果的值
(7)执行命令所需要的时间
输出日志
依上述,将日志输出在控制台是so easy,像这种输出在控制台中只能作为一些小demo,所以应用性不广,如果你要是在window form中或者是Web应用程序中的话,完全可以将日志输出在内存、文件等中。下面演示输出到文件中,其余不予演示。
using (var ctx = new EntityDbContext())
{
var sw = new StreamWriter(@"d:\Data.log") { AutoFlush = true };
ctx.Database.Log = s => {
sw.Write(s);
};
}
结果文件中输出如下:
那么问题来了,如果我们需要将输出的内容进行格式化,那么我们应该怎样通过一种简单的方式怎来做呢?
稍等,容我一一讲来。
日志结果
默认的日志记录器记录sql语句文本,参数以及在命令发到数据库之前带着时间戳的“Executeing”(通过上述文本可知)。一个“Completed”(完成的)包含总的时间被记录在执行命令的下面。
【注意】异步命令中的“Completed”是直到异步任务执行完成、失败或者被取消才被记录下来,当然,它因不同的命令和是否成功执行而产生不同的信息。
执行成功
成功完成输出的是如上述文件中的 执行-- 已在 毫秒内完成,结果为: SqlDataReader
执行失败
通过异常来告知失败,输出中包含了失败的信息。
执行取消
异步命令中的任务被取消会通过异常来告知结果是失败的。因为当试图去取消时,底层的ADO.NET会这样操作,而EF是基于ADO.NET的。
日志格式化
在Database.log属性下我们要充分使用 DatabaseLogFormatter 对象,这个对象有效结合了 IDbCommandInterceptor 来实现,通过接受一个字符串的委托和上下文。这意味着在DatabaseLogFormatter上截取方法之前和通过EF执行命令之后被调用,这些DatabaseLogFormatter方法获取有格式的日志并将其传递给一个委托代理。
通过从DatabaseLogFormatter上派生出一个类并且适当的重写其方法就能实现有格式的日志输出。重写最常用的方法有以下三者:
LogCommand
在命令被执行之前重写此方法来进行更改。通过默认的LogCommand来调用LogParameter的每个参数。当然你也可以进行重写或者处理参数不同。
LogResult
重写此方法来更改原有执行命令后记录下来的结果。
LogParameter
重写此方法来更改其格式和参数所记录的内容。
例如,在每条命令被发送到数据库之前,我们想仅仅通过单行来进行记录。这就需要重写两个方法:
(1)重写 LogCommand 来得到SQL单行的格式
(2)重写 LogResult 不需要做任何动作。(当执行时让其执行可重写的,因为这样在性能上可能会稍微高效一点,但是在功能还是等同的)
依据上述,下面我们来写出代码
public class SingleLineFormatter : DatabaseLogFormatter
{
public SingleLineFormatter(DbContext ctx, Action<string> action)
: base(ctx, action)
{ } public override void LogCommand<TResult>(System.Data.Common.DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
Write(
string.Format("DbContext '{0}' is Executing Command '{1}' '{2}'",
Context.GetType().Name,
command.CommandText.Replace(Environment.NewLine,""),
Environment.NewLine));
base.LogCommand<TResult>(command, interceptionContext);
} public override void LogResult<TResult>(System.Data.Common.DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
base.LogResult<TResult>(command, interceptionContext);
}
}
上述日志输出会调用 Write 方法,此方法将输出发送到配置的写入委托。
【注意】上述代码只是通过一种简单的方式除去换行符,如果是在复杂的SQL语句中,上述方式可能会没有效果。
设置DatabaseLogFormatter
一旦DatabaseLogFormatter的派生类被创建,那么你需要将其注册到EF中,否则也无法进行格式化输出。通过使用Code-Based Configuration,这也就是说,创建一个新的继承自DbConfiguration的类与DbContext上下文类所在同一个程序集,然后会在创建的新类的构造函数中调用 SetDatabaseLogFormatter 方法,该方法接受的参数就是格式化类中构造函数中的两个参数。
public class DbContextConfiguration : DbConfiguration
{
public DbContextConfiguration()
{
SetDatabaseLogFormatter(
(context, action) => new SingleLineFormatter(context, action));
}
}
基于上我们新创建的 SingleLineFormatter 会应用在设置使用Database.log的任何时刻,所以你会看到如下结果:
接下来我们将看 IDbCommandInterceptor 实现直接来控制命令的拦截,并通过集成使用NLog的例子而不使用Database.log。请继续往下看
低级构造块监听
接口监听
监听代码实际上是监听接口的概念。这些接口来继承自 IDbInterceptor 并且当EF有所行为时其定义的方法会被调用。其意义是在每个对象被截获时都有一个接口。例如,当EF调用ExecuteNonQuery, ExecuteScalar, ExecuteReader等相关的方法时在IDbInterceptor上定义的方法将会被调用。同样,当每个操作完成这些方法也会被调用,上述中的DatabaseLogFormatter类就实现了这个接口来记录命令。
接口存在的意义
(1)为了记录SQL命令
(2)为了支持并实现一些其他特性
处理结果
泛型 DbCommandInterceptionContext<> 类里面包含几个属性称之为Result, OriginalResult, Exception, and OriginalException。这些方法会被设置为空通过在操作被执行之前调用的拦截方法。如果操作被成功执行,那么 Result and OriginalResult会被设置成操作的结果。这些值会被在操作执行完后的监听方法所监控。同理,如果操作抛出异常,那么Exception and OriginalException将会被设置。
禁止执行
如果一个监听者在命令快执行完之前设置了Result属性的话,那么EF实际上是不会试图去执行该命令,但是代替的是仅仅是使用结果集。换言之,这个监听者会禁止命令的执行,但是EF会继续,就好像命令已经被执行了。
发生上述禁止执行的示例可能是经过包装的批处理命令,这个监听者会将其储存以便日后将用于批处理但是会装到EF中一如往常的执行该命令。注意监听者需要更多这来实现批处理。
执行后改变结果
如果一个监听者在命令执行完后设置了Result属性,那么EF将会使用改变后的结果而不是通过此操作返回实际的结果。同样,如果一个监听者在命令执行完后设置了Exception属性,那么EF将抛出设置的异常,就好像此操作已经抛出了这个异常一样。
一个监听者也可以设置Exception属性为空来表明没有异常应该被抛出。这其实是非常有意义的,如果执行操作失败但是监听者希望EF继续运行就好像操作成功执行了一样。
OriginalResult 和OriginalException
在EF执行完一个操作后,如果该操作未失败将设置Result和OriginalResult属性,或者如果执行失败抛出异常那么将设置Exception和OrigianlException。
在实际执行完一个操作之后, OriginalResult和OriginalException会被EF设置为只读的。这些属性不会被监听者所设置。这也就意味着,任何监听者能区分Exception(异常)和Result(结果),那些会通过一些其他的相对于操作被执行时发生的真实的异常(Exception)和结果(Result)的监听者所设置。
注册监听者
一旦创建了一个实现了一个或者多个监听接口的类需要通过 DbInterception 注册到EF中。例如:
DbInterception.Add(new NLogCommandInterceptor());
监听者也能够使用基于代码的配置机制(Code-Based DbConfiguration )被注册在应用程序域中。
下面我们将基于以上描述通过使用 IDbCommandInterceptor ,来讲述一个实例: Log To NLog (通过NLog进行日志记录)(关于NLog请看这里:NLog Tutorial)
记录如下日志:
(1)执行非异步的命令作为Warning(警告)
(2)执行抛出的异常作为Error(严重错误)
public class NLogCommandInterceptor : IDbCommandInterceptor
{ private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public void NonQueryExecuting(
DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
} public void NonQueryExecuted(
DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogIfError(command, interceptionContext);
} public void ReaderExecuting(
DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
} public void ReaderExecuted(
DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogIfError(command, interceptionContext);
} public void ScalarExecuting(
DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
} public void ScalarExecuted(
DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
LogIfError(command, interceptionContext);
} private void LogIfNonAsync<TResult>(
DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (!interceptionContext.IsAsync)
{
Logger.Warn("Non-async command used: {0}", command.CommandText);
}
} private void LogIfError<TResult>(
DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (interceptionContext.Exception != null)
{
Logger.Error("Command {0} failed with exception {1}",
command.CommandText, interceptionContext.Exception);
}
} }
结果如下:
【注意】上述NLog得安装如下程序包
接下来在NLog.config中rules和target节点下进行简单的配置输出日志即可,如下:
<rules>
<logger name="ConsoleApplication1.NLogCommandInterceptor" minlevel="Info" writeTo="f"></logger>
/*name为命名空间+实现IDbCommandInterceptor接口的监控类名称,f为写到下面target中的name*/
</rules>
<targets>
<target name="f" xsi:type="File" fileName="c:\temp\log.txt"/>
</targets>
监听中的Dispatch方法
除了在 DbInterception 中注册方法外,它也提供了Dispatch方法,此方法允许代码不是分配到监听的通知的一部分,这是以上已经提到的机制,允许提供者让监听者知道在EF的控制下,一条命令正在被执行。不过对于应用程序开发者去使用Dispatch API这是比较少见的。下面了解下这个方法如何去操作。
DbInterception.Dispatch.Command.NonQueryAsync(myCommand, new DbCommandInterceptionContext());
以上代码做了以下五件事:
(1)确保被设置到监听上下文中的命令是否是 IsAsync 的
(2)调用所有注册在 IDbCommandInterceptors 中的 NonQueryExecuting 方法
(3)除非如以上所说这些 NonQueryExecuting 方法之一被设置了Result属性,否则调用 ExecuteNonQueryAsync
(4)在异步任务中设置了延续,使得注册在 IDbCommandInterceptors 上的所有 NonQueryExecuted 方法都被调用
(5)使得任务结果中包含了可能已经被监听集合中的方法之一改变了的正确结果
打开日志无需重新编译(EF 6.1)
上述我们是通过手动操作来进行日志的输出,如果你嫌麻烦,大可在配置文件(web.config或App.config)中的 EntityFramework 节点下进行相关配置来完成日志输出工作。
(1)输出到控制台
<interceptors>
<interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"/>
</interceptors>
(2)输出到文件
<interceptors>
<interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
<parameters>
<parameter value="c:\temp\log.txt"/>
</parameters>
</interceptor>
</interceptors>
如下结果:
【注意】上述默认情况下,当应用程序每次启动会重新生成一个新的log.text,则此前的将被覆盖。如果该文件总是存在的话,为了追加到到日志文件中,可以进行如下操作:
<interceptors>
<interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
<parameters>
<parameter value="c:\temp\log.txt"/>
<parameter value="true" type="System.Boolean"/>
</parameters>
</interceptor>
</interceptors>
上述就是通过注册监听集合下的interceptor(监听者)来进行日志输出。
IDbConfigurationInterceptor
在EF6.1中引入了此接口,它是一个当应用程序启动时允许代码检测或者修改EF configuration的监听接口。通过这个我们能够实现一个关于 DatabaseLogger 的简单版本
public class ExampleDatabaseLogger : IDbConfigurationInterceptor
{
public void Loaded(
DbConfigurationLoadedEventArgs loadedEventArgs,
DbConfigurationInterceptionContext interceptionContext)
{
var formatterFactory = loadedEventArgs
.DependencyResolver
.GetService<Func<DbContext, Action<string>, DatabaseLogFormatter>>(); var formatter = formatterFactory(null, Console.Write); DbInterception.Add(formatter);
}
}
我们来分析下上述代码:
(1)第一行要求EF去注册 DatabaseLogFormatter 工厂,上述我们只是新建了一个 DatabaseLogFormatter 来代替,当然如果应用程序在此行自定义了格式化 ,那么将通过用此自定义的而不会是默认的
(2)第一行实际上是没有获得DatabaseLogFormatter,它只是获得一个来创建DatabaseLogFormatter实例的一个工厂,第二行调用工厂来获得一个实际意义上的formatter实例。因为我们要记录所有上下文实例,所以为空也是允许的。工厂的第二个参数是对于控制台输出流的委托,因此我们只需将指针指向Console.Write()方法即可。同理如果我们要将日志记录到文件中,则需要通过StreanWrite的实例来实现,上述已经演示。
(3)第三行将DatabaseLogFormatter作为一个监听者来进行注册,在EF中如何就监听者来进行日志记录,上述已经描述。
EntityFramework之Log(五)的更多相关文章
- EntityFramework进阶(五)- 分页
本系列原创博客代码已在EntityFramework6.0.0测试通过,转载请标明出处 我们创建分页信息类CommonPagedList,包含了字段总条数,总页数,当前页码,页大小,当前页数据. us ...
- (转)牛牛牌型判定(五小牛 > 五花牛 > 炸弹 > 银牛 > 牛牛 > 有牛>没牛)
牌型大小: 五小牛 > 五花牛 > 炸弹 > 银牛 > 牛牛 > 有牛(牛987654321) > 没牛,K > Q > J ……2 > A, 黑 ...
- MVC之Session State性能
ASP.NET MVC之Session State性能问题(七) 前言 这一节翻译一篇有关Session State性能问题的文章,非一字一句翻译. 话题 不知道我们在真实环境中是否用到了Sess ...
- Web APi之认证
Web APi之认证(Authentication)两种实现方式后续[三](十五) 前言 之前一直在找工作中,过程也是令人着实的心塞,最后还是稳定了下来,博客也停止更新快一个月了,学如逆水行舟,不 ...
- Ubuntu搭建lnmp环境
1.安装nginx 安装 sudo apt-get install nginx 服务启动.停止.重启 /etc/init.d/nginx start /usr/sbin/nginx -c /etc/n ...
- 1元搭建自己的云服务器&解析域名
最近在学做微信开发,没有自己的域名和服务器就不得不寄人篱下,索性自己就到云主机上搭建了个服务器,但是水平有限弄了一个下午~~有自己的域名和服务器的好处相信不用我多说了.比如日后可以有自己域名的个性博客 ...
- VNC连接远程Ubuntu设置
一.windows 远程软件VNCViewer 这个不多说: 下载地址:http://www.realvnc.com/download/viewer/ 二.安装 vnc-server apt-get ...
- iOS 上架被拒原因保存
一.后台一直在获取用户的定位,需要给用户电池消耗提示 Your app uses the Location Background mode but does not include the requi ...
- UTL_FILE
在PL/SQL中,UTL_FILE包提供文本文件输入和输出功能. 可以访问的目录通过初始化参数UTL_FILE_DIR设置. 注意:UTL_FILE只能读取服务器端文本文件,不能读取二进制文件.这时候 ...
随机推荐
- JavaScript-Object基础知识
1. 定义:对象是JS的核心概念,也是最重要的数据类型.js的所有数据都可以被视为对象. 对象是一种无序的数据集合,由若干个键值对(key:value)构成,由{ ...
- Linux Shell 截取字符串
Linux Shell 截取字符串 shell中截取字符串的方法很多 ${var#*/} ${var##*/} ${var%/*} ${var%%/*} ${var:start:len} ${var: ...
- bzoj 3821: 玄学
题目大意 有一个长度为 n 数列,有若干个事件,事件分为操作和询问两种, 一次操作是把数列[l...r] 区间中的每个元素x变成 ax + b mod p. 一次询问是询问 执行了 第l 次到第r次操 ...
- 续关于C#的微信开发的入门记录一
前几天写了一篇博客<关于C#的微信开发的入门记录一>,原文地址:http://www.cnblogs.com/zhankui/p/4515905.html,现在继续完善: 目前很多小伙伴都 ...
- web应用程序
1.web应用程序和网站的区别 应用程序有两种模式C/S.B/S.C/S是客户端/服务器端程序,也就是说这类程序一般独立运行.而B/S就是浏览器端/服务器端应用程序,这类应用程序一般借助IE等浏览器来 ...
- pythonchallenge 解谜 Level 2
好吧,赶紧贴一下. #-*- coding:utf-8 -*- #代码版本均为python 3.5.1 #Level 2 import re file = open("Level 2.txt ...
- Spring MVC 框架的架包分析,功能作用,优点
由于刚搭建完一个MVC框架,决定分享一下我搭建过程中学习到的一些东西.我觉得不管你是个初级程序员还是高级程序员抑或是软件架构师,在学习和了解一个框架的时候,首先都应该知道的是这个框架的原理和与其有关j ...
- SQL存储过程基础(从基础开始学,加油!)
Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这样就可以提高存储过程的性能. Ø ...
- 非常棒的Android对话框效果
FlycoDialog_Master http://www.see-source.com/androidwidget/detail.html?wid=488 带有各种动画效果的弹出对话框控件.你也可以 ...
- JSP学习笔记
JSP学习笔记 Jsp网页主要分为Elements与Template Data两部分. Template Data:JSP Container不处理的部分,例如HTML内容 Elements:必须经由 ...