最近在学习Entity Framework:Code-First,安装了Entity Framework Power Tools CTP1后,在实际使用过程中遇到了一个问题,即我在app.config中自定义的数据库连接串不能被EF Power Tools识别,使我无法查看由代码生成的实体数据模型。
系统环境:Windows 7 64位旗舰版,Visual Studio 2010旗舰版,Entity Framework 4.2,Entity Framework Power Tools CTP1
这是一个做练习用的Console项目,只有简单的2个文件:POCO.cs里是我Code-First的类,Program.cs里是主程序Main()。刚开始使用的是本地的SQL Server 2008 Express作的数据库。由于EF会自动使用和连接本地的SQL Server Express,因此之前的练习很顺利。每次右键点击POCO.cs,均能正常查看生成的数据模型图,并在数据库正确地生成所有的表。
随后,我为了尝试远程连接,因此在项目中添加了如下内容的app.config
于是问题来了。当我需要查看实体数据模型时,总是在长时间的等待后,得到如下的错误提示。
尽管如此,我运行这个简单的控制台程序,仍能正确地更新和生成数据库中的表。只是无法经由EF Power Tools显示实体数据模型而已。
我尝试了在DbContext的派生类的构造子中传入连接串,尝试了在Main()里设置默认的连接工厂(参见上图背景中第4行代码),也尝试了改为安装Entity Framework 4.1,均未能解决问题。无奈之下,只得四处求助。
最终,我在StackOverflow上的问题得到了Devart的热情解答,给了我一个他们团队的Blog链接 Using Entity Framework Power Tools CTP1 with Oracle, MySQL, PostgreSQL, and SQLite,使得问题得到了较为圆满的解决。在该文中,他们介绍了4种解决方案:
1. 把连接串写入.NET Framework 4.0安装目录下的machine.config里;
2. 把连接串写入Visual Studio 2010的配置文件devenv.exe.config里;
3. 实现一个IDbConnectionFactory的自定义连接工厂,设置项目的缺少连接工厂(就象我此前尝试的一样,但我没放对地方);
4. 使用特定数据库的连接串,并将其传递给DbContext派生类构造子(我之前也尝试了,但我传递的是连接串本身,而不是连接对象)。
经过思考,我最终选择了方案3:自定义连接工厂,并在DbContext派生类的静态构造子中设置项目的数据库缺省连接工厂。
· 自定义的连接工厂
public class PocoConnectionFactory : IDbConnectionFactory { public DbConnection CreateConnection(string nameOrConnectionString) { // connectionString="Data Source=CASPER-PC\SQL_Abbey;Database=BreakAway;Integrated Security=True" var builder = new SqlConnectionStringBuilder { DataSource = @"CASPER-PC\SQL_Abbey", InitialCatalog = @"BreakAway", IntegratedSecurity = true, MultipleActiveResultSets = true }; return new SqlConnection(builder.ToString()); } }
· 在DbContext派生类的静态构造子中设置数据库缺省的连接工厂
public class BreakAwayContext : DbContext { // ... ... static BreakAwayContext() { Database.DefaultConnectionFactory = new PocoConnectionFactory(); } }
在Devart给出的那篇文章里,选择了更为灵活的工厂实现方式,而不是我为了做练习而使用的硬编码方式。 期待EF Power Tools的下个完善版本。
public class DevartConnectionFactory : IDbConnectionFactory { public DevartConnectionFactory(string providerInvariantName, string configFileName) { this.ProviderInvariantName = providerInvariantName; this.ConfigFileName = configFileName; } public DbConnection CreateConnection(string nameOrConnectionString) { if (String.IsNullOrWhiteSpace(nameOrConnectionString)) throw new ArgumentNullException("nameOrConnectionString"); DbProviderFactory providerFactory = DbProviderFactories.GetFactory(ProviderInvariantName); if (providerFactory == null) throw new InvalidOperationException(String.Format("The '{0}' provider is not registered on the local machine.", ProviderInvariantName)); DbConnection connection = providerFactory.CreateConnection(); if (nameOrConnectionString.Contains("=")) connection.ConnectionString = nameOrConnectionString; else { string dbContextClassName = nameOrConnectionString; if (dbContextClassName.Contains('.')) { int classNameFrom = nameOrConnectionString.LastIndexOf('.') + 1; int classNameLength = nameOrConnectionString.Length - classNameFrom; dbContextClassName = nameOrConnectionString.Substring(classNameFrom, classNameLength); } ExeConfigurationFileMap map = new ExeConfigurationFileMap(); map.ExeConfigFilename = this.ConfigFileName; Configuration assemblyConfig = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None); ConnectionStringSettings connectionSettings = assemblyConfig.ConnectionStrings.ConnectionStrings[dbContextClassName]; if (connectionSettings == null) throw new InvalidOperationException(String.Format("Can't find the '{0}' connection string in the config file.", dbContextClassName)); if (connectionSettings.ProviderName != ProviderInvariantName) throw new InvalidOperationException( String.Format( "The '{0}' connection string was expected to be defined for the '{1}' provider, but it is defined for the '{2}' provider.", dbContextClassName, ProviderInvariantName, connectionSettings.ProviderName)); connection.ConnectionString = connectionSettings.ConnectionString; } return connection; } public string ProviderInvariantName { get; set; } public string ConfigFileName { get; set; } }