GVKun编程网logo

EntityFramework之Log

7

这篇文章主要围绕EntityFramework之Log展开,旨在为您提供一份详细的参考资料。我们将全面介绍EntityFramework之Log,同时也会为您带来.net中EntityFrameWor

这篇文章主要围绕EntityFramework之Log展开,旨在为您提供一份详细的参考资料。我们将全面介绍EntityFramework之Log,同时也会为您带来.net 中 EntityFrameWork 的问题、C#中 EF(EntityFramework) 性能优化、Entity FrameWork (实体框架) 是以 ADO.NET Entity FrameWork , 简称为 EF、entity-framework – Entity Framework EntityType’UserAccount’没有定义键的实用方法。

本文目录一览:

EntityFramework之Log

EntityFramework之Log

关于日志

属性日志

DbContext.Database.Log 属性被设置为一个委托,该委托能接受带有一个字符串参数的任何方法,最主要的是,通过设置它到 TextWriterWrite 方法将能应用于任何的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 = 90; stu.Grades.Add(new Grade() { Fraction = 40 }); 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”是直到异步任务执行完成、失败或者被取消才被记录下来,当然,它因不同的命令和是否成功执行而产生不同的信息。

执行成功

成功完成输出的是如上述文件中的 执行-- 已在 0 毫秒内完成,结果为: 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中如何就监听者来进行日志记录,上述已经描述。


原文地址:http://www.cnblogs.com/CreateMyself/p/4753476.html


关注我们的方法:
1.点击文章标题下的“dotNET跨平台”蓝字,或者在微信搜索“opendotnet”,加关注
2.老朋友点击点击右上角“……”标志分享到朋友圈


本文分享自微信公众号 - dotNET跨平台(opendotnet)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

.net 中 EntityFrameWork 的问题

.net 中 EntityFrameWork 的问题

开发.net 时采用 EntityFrameWork,但是使用后,点击任何按钮,页面都会刷新,为什么,有什么解决的办法。。

C#中 EF(EntityFramework) 性能优化

C#中 EF(EntityFramework) 性能优化

现在工作中很少使用原生的sql了,大多数的时候都在使用EF。刚开始的时候,只是在注重功能的实现,最近一段时间在做服务端接口开发。开发的时候也是像之前一样,键盘噼里啪啦的一顿敲,接口秒秒钟上线,但是到联调测试的时候就悲剧了。。。。那叫一个慢啊,客户端有种“千年等一回的赶脚” 。由于访问量和数量都提升了一个数量级,之前没有 考虑过的问题,都在此时暴露了,根据自己百度、google的经历实践,整理了这一些优化点。欢迎各位大神批评指正!

1.使用AsNoTracking(),无跟踪查询,查询出的数据不可以修改,但是可以提高查询速度

 

2.合理使用延迟加载。

如果用不到导航属性中的数据,那么使用懒加载就行了,不会加载不需要的数据到内存中。但是,如果会在 foreach 中使用导航属性中的数据,那么最好是禁用懒加载,通过Include()方法,一次加载全部数据,防止在 foreach 多次和数据库进行交互。当然,一般情况下是这样的,具体的还是要根据当时的业务情况而定。

 

3.判断List中是否含有数据的时候,最好使用Any(),避免使用Count()>0,这么Low的方式,真是慢的一逼啊。

 

4.在where子句中进行中文字符模糊匹配值的时候,记得加上使用DbFunctions.AsNonUnicode("要匹配的字符")。

 

5.错误的使用OrderBy() 导致的错误排序。

要对多个字段进行先后组合排序的时候,正确的是Orderby().ThenBy();切记不要这样啊:OrderBy().OrderBy(),这样达不到你想要的效果,只会按照最后的那个排序字段进行排序。

 

 6.真假分页的问题

line1:query.ToList().Skip((PageIndex - 1) * PageSize).Take(PageSize);

line2: query.Skip((PageIndex - 1) * PageSize).Take(PageSize).ToList();

line1就是加分页,直接从DB中取出全部的数据,然后在内存中进行分页;line2才是真正分页。没有ToList()的时候,返回值类型还是IQueryable<T> ,执行了ToList(),之后直接查询数据库了,返回值类型直接是IEnumerable<T>。

 

7.按需加载部分列。

通过在Select子句中使用select(t=>{t.id,t.name}),只加载需要的列来提升速度。

 

8.使用扩展库Entity Framework Extendeds 进行批量Insert和delete操作,避免生成大量的sql语句和数据库进行多次交互。

 

9.这些做了,性能还是不行?那SqlQuery(),或者通过储存过程,将多条sql语句作为一个提交单元,也可以减少与数据库的交互次数,从而提高性能。

对于EF首次启动慢的问题,可通过下面的措施进行优化:

1.EF的预热问题,在应用程序进行初始化的时候就事先进行预热。比如,mvc应用程序可在global文件中添加如下操作。

protected void Application_Start()
{
    using (var ctx = new mcccEntities())
    {
        var objectContext = ((IObjectContextAdapter)ctx).ObjectContext;
        var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
        mappingCollection.GenerateViews(new List<EdmSchemaError>());
    }

//记得,连接了几个DB就要写几个using啦。

}

 

Entity FrameWork (实体框架) 是以 ADO.NET Entity FrameWork , 简称为 EF

Entity FrameWork (实体框架) 是以 ADO.NET Entity FrameWork , 简称为 EF

Entity FrameWork (实体框架) 是以 ADO.NET Entity FrameWork , 简称为 EF

Entity FrameWork 的特点
  1. 支持多种数据库(MSSQL、Oracle、Mysql 和 DB2)
  2. 强劲的映射引擎,能很好地支持存储过程
  3. 提供 Visual Studio 集成工具,进行可视化操作
  4. 能够与 ASP.NET、WPF、WCF Data Services 进行很好的集成


EF 的优缺点

  EF 的优点
    1. 极大地提高开发效率,开发代码都是强类型的,写代码效率非常高,自动化程序高,采用命令式的编程
    2.EF 提供的模型设计器非常强大,不仅仅带来了设计数据库的革命,其附带来的自动化生成模型代码的功能也极大地提高了开发和架构的效率
    3.EF 跨数据库支持是 ORM 框架的主要功能点之一,有着仅仅通过改变配置就可以做到跨数据库的能力、能换数据库非常方便

  EF 的缺点
    1.EF 性能不好,性能有损耗。(生成 SQL 脚本阶段)在复杂查询的时候生成的 SQL 脚本效率不是很高
    2. 数据库端性能损耗是一样的,但是在将对象状态转换为 SQL 语句时会损失性能

entity-framework – Entity Framework EntityType’UserAccount’没有定义键

entity-framework – Entity Framework EntityType’UserAccount’没有定义键

我收到以下错误:

One or more validation errors were detected during model generation:   
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'UserAccount' has no key defined. Define the key for this EntityType.
\tSystem.Data.Entity.Edm.EdmEntitySet: EntityType: EntitySet 'UserAccounts' is based on type 'UserAccount' that has no keys defined.

此错误由代码触发:

_Db.Database.Initialize(true);

我假设由于某种原因它没有在模型上获取[Key]属性.最初当我尝试运行代码时,我没有添加key属性,这是否意味着已经创建/缓存了某些东西,这阻止了这个密钥的应用?

除了包含Entity Framework 5之外,MVC4项目几乎是一个空白的设置

型号代码

public class AccountContext: DbContext
{
    public AccountContext()
        : base("DefaultConnection")
    {          
    }

    public DbSet<UserAccount> UserAccounts { get; set; }
}

[Table("UserAccount")]
public class UserAccount
{
    [Key]
    [required]        
    public string Username;

    [required]
    [DataType(DataType.Password)]
    [StringLength(100,ErrorMessage = "The {0} must be at least {2} characters long.",MinimumLength = 6)]
    public string Password;  

    public string Name;
    public string Surname;

    [required] 
    public string Email;             
}

Global.asax初始化

Database.Setinitializer(new DropCreateDatabaseAlways<AccountContext>());
var _Db = new AccountContext();
_Db.Database.Initialize(true);

我已经做了一些搜索并理解命名约定,例如Id / UserId等但是我想明确使用[Key]并调用字段Username.

解决方法

我相信EF只允许映射到属性,而不是你使用过的字段.

尝试改变:

[Key]
[required]        
public string Username;

[Key]
[required]        
public string Username { get; set; }

今天的关于EntityFramework之Log的分享已经结束,谢谢您的关注,如果想了解更多关于.net 中 EntityFrameWork 的问题、C#中 EF(EntityFramework) 性能优化、Entity FrameWork (实体框架) 是以 ADO.NET Entity FrameWork , 简称为 EF、entity-framework – Entity Framework EntityType’UserAccount’没有定义键的相关知识,请在本站进行查询。

本文标签:

上一篇微软的FreeBSD社区推广活动 北京站,你没看错!微软现在是一家名副其实的开源公司

下一篇分布式中Redis实现Session终结篇(redis解决分布式session)