ADO.NET Entity Framework CodeFirst 如何输出日志(EF4.3)

来源:互联网 发布:高中数学讲解软件 编辑:程序博客网 时间:2024/04/29 06:30

 

之前写过一篇如何利用 EFProviderWrappers 在EF中增加日志的blog,那篇文章是基于 ModelFirst 来写的,这里在 EF 4.3 CodeFirst 上再次实现。

1. 事前准备
下载 EFProviderWrappers 程序集(点击此处下载),添加:EFProviderWrapperToolkit.dll,EFTracingProvider.dll 引用。
并通过 NuGet 添加 EntityFramework 4.3, Log4Net。

App.config中添加连接字符串,注册 EFProviderWrappers
直接添加了一个空mdf文件(TestDB.mdf)作为Database,注意 CodeFirst 中连接字符串为普通的ADO.NET数据库连接字符串。ModelFirst中则是EntityConnectionString,而这一点也成为后面的一个小障碍。
[html] view plaincopyprint?
  1. <?xmlversion="1.0"encoding="utf-8"?> 
  2. <configuration> 
  3.   <configSections> 
  4.     <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> 
  5.     <sectionname="entityFramework"type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> 
  6.   </configSections> 
  7.   <entityFramework> 
  8.     <defaultConnectionFactorytype="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> 
  9.       <parameters> 
  10.         <parametervalue="Data Source=.\SQLEXPRESS; Integrated Security=True; MultipleActiveResultSets=True"/> 
  11.       </parameters> 
  12.     </defaultConnectionFactory> 
  13.   </entityFramework> 
  14.  
  15.   <!-- 连接字符串 --> 
  16.   <connectionStrings> 
  17.     <addname="testDb"connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;database=TestDB;AttachDBFilename=E:\Programming\VSProject2010\Linq\EFCodeFirstLogTractingTest\EFCodeFirstLogTractingTest\TestDB.mdf;User Instance=true" providerName="System.Data.SqlClient"/> 
  18.   </connectionStrings> 
  19.   <system.data> 
  20.      
  21.   <!-- 注册 EF Provider Wrapper --> 
  22.    <DbProviderFactories> 
  23.       <addname="EF Tracing Data Provider"invariant="EFTracingProvider"description="Tracing Provider Wrapper"type="EFTracingProvider.EFTracingProviderFactory, EFTracingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b"/> 
  24.       <addname="EF Generic Provider Wrapper"invariant="EFProviderWrapper"description="Generic Provider Wrapper"type="EFProviderWrapperToolkit.EFProviderWrapperFactory, EFProviderWrapperToolkit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b"/> 
  25.     </DbProviderFactories> 
  26.   </system.data> 
  27.  
  28. </configuration> 

2. 添加 Model, Entities, EntitiesInitializer
实体:Memo.cs
[csharp] view plaincopyprint?
  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using System.ComponentModel.DataAnnotations; 
  6.  
  7. namespace EFCodeFirstLogTractingTest.Models 
  8.     [Table("Memo")] 
  9.     public class Memo 
  10.     { 
  11.         [Key] 
  12.         [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
  13.         public int Id {get; set; } 
  14.  
  15.         [Required] 
  16.         public string Title {get; set; } 
  17.     } 

TestDbEntities.cs

[csharp] view plaincopyprint?
  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using System.Data.Entity; 
  6.  
  7. namespace EFCodeFirstLogTractingTest.Models 
  8.     public class TestDbEntities : DbContext 
  9.     { 
  10.         public TestDbEntities() 
  11.             : base("testDb"
  12.         { } 
  13.  
  14.         public DbSet<Memo> Memos {get; set; } 
  15.     } 
TestDbEntitiesInitializer.cs
它负责初始化数据库,添加3条数据。
[csharp] view plaincopyprint?
  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using System.Data.Entity; 
  6.  
  7. namespace EFCodeFirstLogTractingTest.Models 
  8.     public class TestDbEntitiesInitializer : DropCreateDatabaseAlways<TestDbEntities> 
  9.     { 
  10.         protected override void Seed(TestDbEntities context) 
  11.         { 
  12.             base.Seed(context); 
  13.  
  14.             new List<Memo> 
  15.             { 
  16.                 new Memo { Title="memo1" }, 
  17.                 new Memo { Title="memo2" }, 
  18.                 new Memo { Title="memo3" }, 
  19.             }.ForEach(m => context.Memos.Add(m)); 
  20.         } 
  21.     } 
好了,CodeFirst已经可以工作了。

3. 添加 EFProviderWrappers
前面的经验告诉我,EFTracingProvider 通过扩展方法为 ObjectContext 添加了 GetTractingConnection(),TracingConnection里的 CommandExecuting 事件正是我们需要监听 Linq 转化成真正 SQL 的时机。
于是,我将上面的 TestDbEntities.cs 修改为如下,这样执行 Linq2EF 语句时,就能在控制台输出实际的SQL了。
(注意:此时 nameOrConnectionString 传入的是 name=testDb 而不是 testDb)
[csharp] view plaincopyprint?
  1. public TestDbEntities() 
  2.     : this("name=testDb"
  3. { } 
  4.  
  5. public TestDbEntities(string nameOrConnectionString) 
  6.     : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(nameOrConnectionString),true
  7.     var ctx = ((IObjectContextAdapter)this).ObjectContext; 
  8.     ctx.GetTracingConnection().CommandExecuting += (s, e) => 
  9.     { 
  10.         Console.WriteLine(e.ToTraceString()); 
  11.     }; 
看看好使不?Oh NO~ 得到下面的异常:
[html] view plaincopyprint?
  1. System.ArgumentException was unhandled 
  2.   HResult=-2147024809 
  3.   Message=The 'data source' keyword is not supported. 
  4.   Source=System.Data.Entity 
  5.   StackTrace: 
  6.        at System.Data.EntityClient.EntityConnectionStringBuilder.set_Item(String keyword, Object value) 
  7.        at System.Data.Common.DbConnectionStringBuilder.set_ConnectionString(String value) 
  8.        at EFProviderWrapperToolkit.EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(String entityConnectionString, String[] wrapperProviders) 
  9.        ... 
看了下 EFProviderWrappers 源码,才发现 EFProviderWrappers 只接受一个 EntityConnectionString(MSDN)。搜了下,按照下面的解决方法:
添加一个 EFTracingUtil 类:(别忘记添加 System.Configuration.dll)
[csharp] view plaincopyprint?
  1. public class EFTracingUtil 
  2.     { 
  3.         public static DbConnection GetConnection(string nameOrConnectionString) 
  4.         { 
  5.             try 
  6.             { 
  7.                 // this only supports entity connection strings http://msdn.microsoft.com/en-us/library/cc716756.aspx 
  8.                 return EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(nameOrConnectionString); 
  9.             } 
  10.             catch (ArgumentException) 
  11.             { 
  12.                 if (nameOrConnectionString.Contains('=')) 
  13.                 { 
  14.                     nameOrConnectionString = nameOrConnectionString.Substring(nameOrConnectionString.IndexOf('=') + 1); 
  15.                 } 
  16.                 // an invalid entity connection string is assumed to be a normal connection string name or connection string (Code First) 
  17.                 ConnectionStringSettings connectionStringSetting = 
  18.                     ConfigurationManager.ConnectionStrings[nameOrConnectionString]; 
  19.                 string connectionString; 
  20.                 string providerName; 
  21.  
  22.                 if (connectionStringSetting !=null
  23.                 { 
  24.                     connectionString = connectionStringSetting.ConnectionString; 
  25.                     providerName = connectionStringSetting.ProviderName; 
  26.                 } 
  27.                 else 
  28.                 { 
  29.                     providerName = "System.Data.SqlClient"
  30.                     connectionString = nameOrConnectionString; 
  31.                 } 
  32.  
  33.                 return CreateTracingConnection(connectionString, providerName); 
  34.             } 
  35.         } 
  36.  
  37.         private static EFTracingConnection CreateTracingConnection(string connectionString,string providerInvariantName) 
  38.         { 
  39.             string wrapperConnectionString = 
  40.                 String.Format(@"wrappedProvider={0};{1}", providerInvariantName, connectionString); 
  41.  
  42.             EFTracingConnection connection = 
  43.                 new EFTracingConnection 
  44.                 { 
  45.                     ConnectionString = wrapperConnectionString 
  46.                 }; 
  47.  
  48.             return connection; 
  49.         } 
  50.     } 
把 TestDbEntities 的构造函数修改为如下:
[csharp] view plaincopyprint?
  1. public TestDbEntities(string nameOrConnectionString) 
  2.     : base(<strong>EFTracingUtil.GetConnection</strong>(nameOrConnectionString),true
  3.     var ctx = ((IObjectContextAdapter)this).ObjectContext; 
  4.     ctx.GetTracingConnection().CommandExecuting += (s, e) => 
  5.     { 
  6.         Console.WriteLine(e.ToTraceString()); 
  7.     }; 
再来试试,Oh Shit 还是有异常!
[html] view plaincopyprint?
  1. System.ArgumentException was unhandled 
  2.   HResult=-2147024809 
  3.   Message=The provider manifest given is not of type 'System.Data.SqlClient.SqlProviderManifest'. 
  4.   Source=System.Data.Entity 
  5.   StackTrace: 
  6.        at System.Data.SqlClient.SqlProviderServices.GetSqlVersion(StoreItemCollection storeItemCollection) 
  7.        at System.Data.SqlClient.SqlProviderServices.DbCreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection) 
  8.        at System.Data.Common.DbProviderServices.CreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection) 
  9.        at EFProviderWrapperToolkit.DbProviderServicesBase.DbCreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection) 
  10.        at System.Data.Objects.ObjectContext.CreateDatabase() 
  11. ...... 
错误来源很可能是 SqlClient 内部的,它预期得到 SqlProviderManifest 但得到的是 DbProviderManifestWrapper。只好绕道了,通过 Database.SetInitializer 不行... 还好EF 4.3的新特性:数据库迁移。在 Package Manager Console 里:Enable-Migrations
运行命令还是会得到下面的错误:
[html] view plaincopyprint?
  1. System.Collections.Generic.KeyNotFoundException: The given key was not  
  2. present in the dictionary. 
其实这个原因是因为我的 TestDbEntities() 无参数的默认构造方法:
[csharp] view plaincopyprint?
  1. public TestDbEntities() 
  2.     : this("name=testDb"
  3. { } 
刚才改为 "name=testDb", 而 Migrations 只认 "testDb" (因为它直接到 config 文件中读取 ConnectionString)
解决方法有两个:
(1) 改回去,将默认的构造方法修改为:
[csharp] view plaincopyprint?
  1. public TestDbEntities() 
  2.     : base("testDb"
  3. { } 
注意:外部使用时如果想要 Tracing 就不能用默认构造方法了,因为它没用注册 CommandExecuting 事件处理。
(2) 修改 Migrations.Config 里的 TargetDatabase,这里是允许开发者直接修改它来实现各种复杂的数据迁移的。
[csharp] view plaincopyprint?
  1. public Configuration() 
  2.     var connectionString = ConfigurationManager.ConnectionStrings["TestDb"].ConnectionString; 
  3.     TargetDatabase = new DbConnectionInfo(connectionString,"System.Data.SqlClient"); 
  4.     AutomaticMigrationsEnabled = true

这里还是用了方法(1),那么最后的使用如下:
[csharp] view plaincopyprint?
  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using System.Data.Entity; 
  6. using EFCodeFirstLogTractingTest.Models; 
  7. using System.Data.Entity.Migrations; 
  8.  
  9. namespace EFCodeFirstLogTractingTest 
  10.     class Program 
  11.     { 
  12.         static void Main(string[] args) 
  13.         { 
  14.             //Database.SetInitializer<TestDbEntities>(new TestDbEntitiesInitializer()); 
  15.             Migrations.Configuration config = new Migrations.Configuration(); 
  16.             var migrator = new DbMigrator(config); 
  17.             migrator.Update(); 
  18.  
  19.             using (var db = new TestDbEntities("name=testDb")) 
  20.             { 
  21.                 var result = db.Memos.OrderBy(m => m.Id).Skip(2).First(); 
  22.                 Console.WriteLine(result.Title); 
  23.             } 
  24.  
  25.             Console.Read(); 
  26.         } 
  27.     } 


利用 Log4Net 就不用说了,可以参看我前面这篇文章。

原创粉丝点击