Entity Framework CTP 4-Primer inicializador de base de datos personalizado de código
entity-framework code-first (6)
Me gustaría implementar una estrategia de inicialización de base de datos personalizada para que pueda generar el esquema de la base de datos y aplicarlo a una base de datos SQL VACÍA EXISTENTE utilizando una ID de usuario y una contraseña provistas.
Lamentablemente, las estrategias integradas no proporcionan lo que estoy buscando:
// 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>());
He resuelto lo siguiente, pero esto no crea el ModelHash, así que no puedo usar "context.Database.ModelMatchesDatabase ()" para validar que el esquema de la base de datos se haya creado y evitar múltiples inicializaciones:
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();
}
}
Preguntas:
¿Alguien sabe cómo puedo obtener / crear el hash modelo? (que es una entidad EdmMetadata)
-O-
¿Hay una forma mejor de hacer esto en general usando Code First CTP?
¡Esta es la forma más fácil de ejecutar EF Code First en AppHarbor !
Usar la función EdmMetadata.TryGetModelHash(context)
para verificar cuándo el modelo no coincide con la base de datos y mostrar un error con el nuevo código que debe usarse después de ejecutar los scripts de alteración.
PopulateOnly: solo crea objetos cuando la base de datos está vacía
Pensé en publicar mi propia versión del Inicializador que estoy usando en el appharbor para completar una base de datos existente . También intentará hacer una creación si la base de datos no existe y arroja si se detecta un cambio (lo siento, todavía no hay actualizaciones automáticas). Espero que alguien lo encuentre útil.
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";
}
}
El Power Pack de Generación de Base de Datos de Entity Designer hará esto. No estoy seguro de si funciona con Code First todavía, pero vale la pena intentarlo.
Solo para contribuir a la solución de @ Luhmann, aquí está el mío pero ligeramente cambiado para dejar caer FK y PK correctamente.
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''";
}
}
Tomé un enfoque ligeramente diferente a este problema. Este parece ser un lugar tan bueno como cualquiera para compartir los resultados.
Solo quiero crear tablas que no existan en la base de datos. Esto tiene la ventaja de poder desplegar nuevas tablas sin borrar el resto de la base de datos.
Esto también ayuda si tiene múltiples contextos de datos en una cadena de herencia. Por ejemplo, si divide su aplicación en diferentes conjuntos. Puede tener un contexto de datos en un módulo "core" y luego heredarlo en un ensamblaje diferente para módulos adicionales. Esta configuración funciona bien, pero a los inicializadores Drop / Create incorporados no les gusta porque el hash del modelo está cambiando todo el tiempo. Al verificar la existencia de la tabla, la inicialización tarda un poco más, pero no tiene ninguno de estos problemas.
De todos modos, aquí está el código:
/// <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"
);
}
}
}
}
Tuve el mismo problema. Realmente no lo resolví, pero me las arreglé para ponerme en funcionamiento un poco desagradable, así que puedo implementar mi solución en AppHarbor;)
Es una implementación de IDatabaseInitializer, que no elimina el db, sino que ataca todas las restricciones y tablas, y luego usa el método ObjectContext.CreateDatabaseScript () para generar el sql, y luego lo ejecuto como un comando de tienda. Muy similar a la implementación anterior en la pregunta.
Pero también agregué funcionalidad para crear un hash del modelo y guardarlo en db, y cuando se ejecuta nuevamente, comprueba si el hash de modelo actual coincide con el que db. Al igual que la implementación real de código primero.
No pude hacer que funcione con la construcción en contexto. Base de datos.Compatible con modelo (verdadero) - pero esto debería funcionar igual de bien, y dado que es una solución temporal, debería estar bien.
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''";
}
}
Yo también estaba buscando una buena solución ya que godaddy no permite la caída / creación de la base de datos y, por lo tanto, no se crean tablas. Dado que la versión más reciente de Entity Framework tiene EDMData obsoleto, modifiqué el código de Alex para ver si existe o no una tabla DropMeToRecreateDatabase, si no existe, elimina todas las tablas y recrea tablas nuevas.
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''";
}
}