entity frameworkCTP 4 – 代码第一个自定义数据库初始化程序
我想实现一个自定义的数据库初始化策略,以便我可以生成数据库模式并使用提供的用户ID和密码将其应用到EXISTING EMPTY SQL数据库。
不幸的是内置的策略不提供我在找什么:
// The default strategy creates the DB only if it doesn't exist - but it does // exist so this does nothing Database.SetInitializer(new CreateDatabaseOnlyIfNotExists<DataContext>()); // Drops and re-creates the database but then this breaks my security mapping and // only works if using a “Trusted" connection Database.SetInitializer(new RecreateDatabaseIfModelChanges<DataContext>()); // Strategy for always recreating the DB every time the app is run. – no good for // what I want Database.SetInitializer(new AlwaysRecreateDatabase<DataContext>());
我已经解决了以下问题,但是这不会创buildModelHash,所以我无法使用“context.Database.ModelMatchesDatabase()”来validation数据库模式已经创build并防止多重初始化:
public class Initializer : IDatabaseInitializer<DataContext> { Public void InitializeDatabase(DataContext context) { // this generates the SQL script from my POCO Classes var sql = context.ObjectContext.CreateDatabaseScript(); // As expected - when run the second time it bombs out here with "there is already an // object named xxxxx in the database" context.ObjectContext.ExecuteStoreCommand(sql); this.seed(context) context.SaveChanges(); } }
问题:
有谁知道我怎么可以得到/创build模型哈希? (这是一个EdmMetadata实体)
-要么-
通常使用Code First CTP有没有更好的方法?
我遇到了同样的问题。 我没有真正解决它,但我设法得到一个令人讨厌的解决方法运行,所以我可以将我的解决scheme部署到AppHarbor;)
它是一个IDatabaseInitializer实现,它不会删除数据库,而只是对所有约束和表进行核对,然后使用ObjectContext.CreateDatabaseScript()方法生成sql,然后将其作为一个store命令执行。 很像上面的问题中的实现。
但是我也添加了从模型创build哈希的function,并将其保存在数据库中,当它再次运行时,它会检查当前的模型哈希是否与一个数据库相匹配。 就像真正的代码优先实现一样。
我不能让它在context.Database.CompatibleWithModel(true)中的构build工作 – 但是这应该工作得很好,看作是一个临时的解决方法,应该没问题。
using System; using System.Data.Entity; using System.Data.Entity.Database; using System.Data.Entity.Design; using System.Data.Entity.Infrastructure; using System.Data.Metadata.Edm; using System.Data.Objects; using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Xml; using System.Linq; namespace Devtalk { public class DontDropDbJustCreateTablesIfModelChanged<T> : IDatabaseInitializer<T> where T : DbContext { private EdmMetadata _edmMetaData; public void InitializeDatabase(T context) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; string modelHash = GetModelHash(objectContext); if (CompatibleWithModel(modelHash, context, objectContext)) return; DeleteExistingTables(objectContext); CreateTables(objectContext); SaveModelHashToDatabase(context, modelHash, objectContext); } private void SaveModelHashToDatabase(T context, string modelHash, ObjectContext objectContext) { if (_edmMetaData != null) objectContext.Detach(_edmMetaData); _edmMetaData = new EdmMetadata(); context.Set<EdmMetadata>().Add(_edmMetaData); _edmMetaData.ModelHash = modelHash; context.SaveChanges(); } private void CreateTables(ObjectContext objectContext) { string dataBaseCreateScript = objectContext.CreateDatabaseScript(); objectContext.ExecuteStoreCommand(dataBaseCreateScript); } private void DeleteExistingTables(ObjectContext objectContext) { objectContext.ExecuteStoreCommand(Dropallconstraintsscript); objectContext.ExecuteStoreCommand(Deletealltablesscript); } private string GetModelHash(ObjectContext context) { var csdlXmlString = GetCsdlXmlString(context).ToString(); return ComputeSha256Hash(csdlXmlString); } private bool CompatibleWithModel(string modelHash, DbContext context, ObjectContext objectContext) { var isEdmMetaDataInStore = objectContext.ExecuteStoreQuery<int>(LookupEdmMetaDataTable).FirstOrDefault(); if (isEdmMetaDataInStore == 1) { _edmMetaData = context.Set<EdmMetadata>().FirstOrDefault(); if (_edmMetaData != null) { return modelHash == _edmMetaData.ModelHash; } } return false; } private string GetCsdlXmlString(ObjectContext context) { if (context != null) { var entityContainerList = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace); if (entityContainerList != null) { EntityContainer entityContainer = entityContainerList.FirstOrDefault(); var generator = new EntityModelSchemaGenerator(entityContainer); var stringBuilder = new StringBuilder(); var xmlWRiter = XmlWriter.Create(stringBuilder); generator.GenerateMetadata(); generator.WriteModelSchema(xmlWRiter); xmlWRiter.Flush(); return stringBuilder.ToString(); } } return string.Empty; } private static string ComputeSha256Hash(string input) { byte[] buffer = new SHA256Managed().ComputeHash(Encoding.ASCII.GetBytes(input)); var builder = new StringBuilder(buffer.Length * 2); foreach (byte num in buffer) { builder.Append(num.ToString("X2", CultureInfo.InvariantCulture)); } return builder.ToString(); } private const string Dropallconstraintsscript = @"select 'ALTER TABLE ' + so.table_name + ' DROP CONSTRAINT ' + so.constraint_name from INFORMATION_SCHEMA.TABLE_CONSTRAINTS so"; private const string Deletealltablesscript = @"declare @cmd varchar(4000) declare cmds cursor for Select 'drop table [' + Table_Name + ']' From INFORMATION_SCHEMA.TABLES open cmds while 1=1 begin fetch cmds into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds deallocate cmds"; private const string LookupEdmMetaDataTable = @"Select COUNT(*) FROM INFORMATION_SCHEMA.TABLES T Where T.TABLE_NAME = 'EdmMetaData'"; } }
这是在AppHarbor上运行EF Code First的最简单方法!
使用EdmMetadata.TryGetModelHash(context)
函数来检查模型何时与数据库不匹配,并在运行修改脚本之后显示需要使用的新代码的错误。
PopulateOnly:只在数据库为空时创build对象
我以为我会发布我自己版本的初始化程序,我正在使用的应用程序来填充现有的数据库 。 如果数据库不存在,也会尝试创build,如果检测到更改,则会抛出 (遗憾的是没有自动更新)。 我希望有人觉得它有用。
using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Transactions; namespace Deskspace.EntityFramework { /// <summary> A Database Initializer for appharbor </summary> /// <typeparam name="T">Code first context</typeparam> public class PopulateOnly<T> : IDatabaseInitializer<T> where T : DbContext { private EdmMetadata metadata; private enum Status { Compatable, Invalid, Missing } /// <summary> Initializer that supports creating or populating a missing or empty database </summary> /// <param name="context"> Context to create for </param> public void InitializeDatabase(T context) { // Get metadata hash string hash = EdmMetadata.TryGetModelHash(context); bool exists; using (new TransactionScope( TransactionScopeOption.Suppress )) { exists = context.Database.Exists(); } if (exists) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; var dbHash = GetHashFromDatabase( objectContext ); Status compatability = string.IsNullOrEmpty( dbHash )? Status.Missing : (dbHash != hash)? Status.Invalid : Status.Compatable; if (compatability == Status.Missing) { // Drop all database objects ClearDatabase( objectContext ); // Recreate database objects CreateTables( objectContext ); // Save the new hash SaveHash( objectContext, hash ); } else if (compatability == Status.Invalid) { throw new Exception( "EdmMetadata does not match, manually update the database, expected: " + Environment.NewLine + "<[(" + hash + ")}>" ); } } else { context.Database.Create(); context.SaveChanges(); } } private void ClearDatabase(ObjectContext objectContext) { objectContext.ExecuteStoreCommand( DropAllObjects ); } private void CreateTables(ObjectContext objectContext) { string dataBaseCreateScript = objectContext.CreateDatabaseScript(); objectContext.ExecuteStoreCommand( dataBaseCreateScript ); } private void SaveHash(ObjectContext objectContext, string hash) { objectContext.ExecuteStoreCommand( string.Format(UpdateEdmMetaDataTable, hash.Replace( "'", "''" )) ); } private string GetHashFromDatabase(ObjectContext objectContext) { foreach (var item in objectContext.ExecuteStoreQuery<string>( GetEdmMetaDataTable )) { return item; } return string.Empty; } private const string UpdateEdmMetaDataTable = @" Delete From EdmMetadata; Insert Into EdmMetadata (ModelHash) Values ('{0}');"; private const string GetEdmMetaDataTable = @" If Exists (Select * From INFORMATION_SCHEMA.TABLES tables where tables.TABLE_NAME = 'EdmMetaData') Select Top 1 ModelHash From EdmMetadata; Else Select '';"; private const string DropAllObjects = @" declare @n char(1) set @n = char(10) declare @stmt nvarchar(max) -- procedures select @stmt = isnull( @stmt + @n, '' ) + 'drop procedure [' + name + ']' from sys.procedures -- check constraints select @stmt = isnull( @stmt + @n, '' ) + 'alter table [' + object_name( parent_object_id ) + '] drop constraint [' + name + ']' from sys.check_constraints -- functions select @stmt = isnull( @stmt + @n, '' ) + 'drop function [' + name + ']' from sys.objects where type in ( 'FN', 'IF', 'TF' ) -- views select @stmt = isnull( @stmt + @n, '' ) + 'drop view [' + name + ']' from sys.views -- foreign keys select @stmt = isnull( @stmt + @n, '' ) + 'alter table [' + object_name( parent_object_id ) + '] drop constraint [' + name + ']' from sys.foreign_keys -- tables select @stmt = isnull( @stmt + @n, '' ) + 'drop table [' + name + ']' from sys.tables -- user defined types select @stmt = isnull( @stmt + @n, '' ) + 'drop type [' + name + ']' from sys.types where is_user_defined = 1 exec sp_executesql @stmt"; } }
只要为@卢曼的解决scheme作出贡献,这里是我的,但稍微改变,以适当放弃FK和PK。
using System.Data.Entity; using System.Data.Entity.Design; using System.Data.Entity.Infrastructure; using System.Data.Metadata.Edm; using System.Data.Objects; using System.Globalization; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Xml; namespace SISQuote.Server.Persistence { public class DontDropExistingDbCreateTablesIfModelChanged<T> : IDatabaseInitializer<T> where T : DbContext { private EdmMetadata edmMetaData; public bool TryInitializeDatabase(T context) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; string modelHash = GetModelHash(objectContext); if (CompatibleWithModel(modelHash, context, objectContext)) return false; DeleteExistingTables(objectContext); CreateTables(objectContext); SaveModelHashToDatabase(context, modelHash, objectContext); return true; } public void InitializeDatabase(T context) { TryInitializeDatabase(context); } private void SaveModelHashToDatabase(T context, string modelHash, ObjectContext objectContext) { if (edmMetaData != null) objectContext.Detach(edmMetaData); edmMetaData = new EdmMetadata(); context.Set<EdmMetadata>().Add(edmMetaData); edmMetaData.ModelHash = modelHash; context.SaveChanges(); } private void CreateTables(ObjectContext objectContext) { string dataBaseCreateScript = objectContext.CreateDatabaseScript(); objectContext.ExecuteStoreCommand(dataBaseCreateScript); } private void DeleteExistingTables(ObjectContext objectContext) { objectContext.ExecuteStoreCommand(DeleteAllTablesScript); } private string GetModelHash(ObjectContext context) { var csdlXmlString = GetCsdlXmlString(context).ToString(); return ComputeSha256Hash(csdlXmlString); } public bool CompatibleWithModel(DbContext context) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return CompatibleWithModel(GetModelHash(objectContext), context, objectContext); } private bool CompatibleWithModel(string modelHash, DbContext context, ObjectContext objectContext) { var isEdmMetaDataInStore = objectContext.ExecuteStoreQuery<int>(LookupEdmMetaDataTable).FirstOrDefault(); if (isEdmMetaDataInStore == 1) { edmMetaData = context.Set<EdmMetadata>().FirstOrDefault(); if (edmMetaData != null) { return modelHash == edmMetaData.ModelHash; } } return false; } private string GetCsdlXmlString(ObjectContext context) { if (context != null) { var entityContainerList = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace); if (entityContainerList != null) { EntityContainer entityContainer = entityContainerList.FirstOrDefault(); var generator = new EntityModelSchemaGenerator(entityContainer); var stringBuilder = new StringBuilder(); var xmlWRiter = XmlWriter.Create(stringBuilder); generator.GenerateMetadata(); generator.WriteModelSchema(xmlWRiter); xmlWRiter.Flush(); return stringBuilder.ToString(); } } return string.Empty; } private static string ComputeSha256Hash(string input) { byte[] buffer = new SHA256Managed().ComputeHash(Encoding.ASCII.GetBytes(input)); var builder = new StringBuilder(buffer.Length * 2); foreach (byte num in buffer) { builder.Append(num.ToString("X2", CultureInfo.InvariantCulture)); } return builder.ToString(); } private const string DeleteAllTablesScript = @"declare @cmd varchar(4000) DECLARE cmds0 CURSOR FOR SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' DECLARE cmds1 CURSOR FOR SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS DECLARE cmds2 CURSOR FOR SELECT 'TRUNCATE TABLE ' + TABLE_NAME FROM INFORMATION_SCHEMA.TABLES DECLARE cmds3 CURSOR FOR SELECT 'DROP TABLE [' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES open cmds0 while 1=1 begin fetch cmds0 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds0 deallocate cmds0 open cmds1 while 1=1 begin fetch cmds1 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds1 deallocate cmds1 open cmds2 while 1=1 begin fetch cmds2 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds2 deallocate cmds2 open cmds3 while 1=1 begin fetch cmds3 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds3 deallocate cmds3"; private const string LookupEdmMetaDataTable = @"Select COUNT(*) FROM INFORMATION_SCHEMA.TABLES T Where T.TABLE_NAME = 'EdmMetaData'"; } }
我采取了一个稍微不同的方法解决这个问题。 这似乎是一个很好的地方,任何分享的结果。
我只想创build数据库中不存在的表。 这有利于能够在不擦除数据库的其余部分的情况下推出新表。
如果在inheritance链中有多个数据上下文,这也有帮助。 例如,如果将应用程序拆分为不同的程序集。 您可能在“核心”模块中有一个数据上下文,然后在不同的程序集中inheritance它以用于附加模块。 此configuration工作正常,但内置的Drop / Create初始化程序不喜欢它,因为模型散列一直在改变。 通过检查表存在,初始化需要稍微更长,但是你没有这些问题。
无论如何,这是代码:
/// <summary> /// Database Initializer to create tables only if they don't already exist. /// It will never drop the database. Does not check the model for compatibility. /// </summary> /// <typeparam name="TContext">The data context</typeparam> public class CreateTablesOnlyIfTheyDontExist<TContext> : IDatabaseInitializer<TContext> where TContext : DataContext { public void InitializeDatabase(TContext context) { using (new TransactionScope(TransactionScopeOption.Suppress)) { // If the database doesn't exist at all then just create it like normal. if (!context.Database.Exists()) { context.Database.Create(); return; } // get the object context var objectContext = ((IObjectContextAdapter)context).ObjectContext; // get the database creation script var script = objectContext.CreateDatabaseScript(); if (context.Database.Connection is SqlConnection) { // for SQL Server, we'll just alter the script // add existance checks to the table creation statements script = Regex.Replace(script, @"create table \[(\w+)\]\.\[(\w+)\]", "if not exists (select * from INFORMATION_SCHEMA.TABLES " + "where TABLE_SCHEMA='$1' and TABLE_NAME = '$2')\n$&"); // add existance checks to the table constraint creation statements script = Regex.Replace(script, @"alter table \[(\w+)\]\.\[(\w+)\] add constraint \[(\w+)\]", "if not exists (select * from INFORMATION_SCHEMA.TABLE_CONSTRAINTS " + "where TABLE_SCHEMA='$1' and TABLE_NAME = '$2' " + "and CONSTRAINT_NAME = '$3')\n$&"); // run the modified script objectContext.ExecuteStoreCommand(script); } else if (context.Database.Connection is SqlCeConnection) { // SQL CE doesn't let you use inline existance checks, // so we have to parse each statement out and check separately. var statements = script.Split(new[] { ";\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (var statement in statements) { var quoteSplitStrings = statement.Split('"'); if (statement.StartsWith("CREATE TABLE")) { // Create a table if it does not exist. var tableName = quoteSplitStrings[1]; const string sql = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME='{0}'" var checkScript = string.Format(sql, tableName); if (objectContext.ExecuteStoreQuery<int>(checkScript).First() == 0) objectContext.ExecuteStoreCommand(statement); } else if (statement.Contains("ADD CONSTRAINT")) { // Add a table constraint if it does not exist. var tableName = quoteSplitStrings[1]; var constraintName = quoteSplitStrings[3]; const string sql = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " + "WHERE TABLE_NAME='{0}' AND CONSTRAINT_NAME='{1}'"; var checkScript = string.Format(sql, tableName, constraintName); if (objectContext.ExecuteStoreQuery<int>(checkScript).First() == 0) objectContext.ExecuteStoreCommand(statement); } else { // Not sure what else it could be. Just run it. objectContext.ExecuteStoreCommand(statement); } } } else { throw new InvalidOperationException( "This initializer is only compatible with SQL Server or SQL Compact Edition" ); } } } }
我也一直在寻找一个很好的解决scheme,因为godaddy不允许删除/创build数据库,因此没有创build表。 由于entity framework的较新版本已过时EDMData,我修改了Alex的代码,看是否存在DropMeToRecreateDatabase表,如果它不存在,它将删除所有表并重新创build新表。
using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Linq; namespace LadyTreble.DatabaseInitializer { public class DontDropExistingDbCreateTablesIfTableDropped<T> : IDatabaseInitializer<T> where T : DbContext { public bool TryInitializeDatabase(T context) { var objectContext = ((IObjectContextAdapter)context).ObjectContext; if (objectContext.ExecuteStoreQuery<int>(GetTableCount).FirstOrDefault() == 0) { this.DeleteExistingTables(objectContext); this.CreateTables(objectContext); } return true; } public void InitializeDatabase(T context) { this.TryInitializeDatabase(context); } private void CreateTables(ObjectContext objectContext) { string dataBaseCreateScript = objectContext.CreateDatabaseScript(); objectContext.ExecuteStoreCommand(dataBaseCreateScript); } private void DeleteExistingTables(ObjectContext objectContext) { objectContext.ExecuteStoreCommand(DeleteAllTablesScript); } private const string DeleteAllTablesScript = @"declare @cmd varchar(4000) DECLARE cmds0 CURSOR FOR SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' DECLARE cmds1 CURSOR FOR SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS DECLARE cmds2 CURSOR FOR SELECT 'TRUNCATE TABLE ' + TABLE_NAME FROM INFORMATION_SCHEMA.TABLES DECLARE cmds3 CURSOR FOR SELECT 'DROP TABLE [' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES open cmds0 while 1=1 begin fetch cmds0 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds0 deallocate cmds0 open cmds1 while 1=1 begin fetch cmds1 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds1 deallocate cmds1 open cmds2 while 1=1 begin fetch cmds2 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds2 deallocate cmds2 open cmds3 while 1=1 begin fetch cmds3 into @cmd if @@fetch_status != 0 break print @cmd exec(@cmd) end close cmds3 deallocate cmds3 CREATE TABLE DropMeToRecreateDatabase(id int IDENTITY(1,1) NOT NULL)"; private const string GetTableCount = @"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME = 'DropMeToRecreateDatabase'"; } }
实体devise器数据库生成电源包将执行此操作。 不知道它是否与Code First一起工作,但值得一试。