Entity Framework CTP 4 - первый инициализатор пользовательского кода базы данных - PullRequest
27 голосов
/ 27 октября 2010

Я хотел бы реализовать собственную стратегию инициализации базы данных, чтобы я мог сгенерировать схему базы данных и применить ее к базе данных 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>());

Я разработал следующее, но это не создает ModelHash, поэтому я не могу использовать "context.Database.ModelMatchesDatabase ()" , чтобы проверить, что схема базы данных была создана и предотвратить несколько инициализация:

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();
    }
}  

Вопросы:

Кто-нибудь знает, как я могу получить / создать хеш модели? (который является объектом EdmMetadata)

-Или-

Есть ли лучший способ сделать это в целом с использованием CTP Code First?

Ответы [ 6 ]

24 голосов
/ 16 февраля 2011

Я столкнулся с той же проблемой. На самом деле я не решил, но мне удалось запустить небольшой неприятный обходной путь, поэтому я могу развернуть свое решение в AppHarbor;)

Это реализация IDatabaseInitializer, которая не удаляет БД, а просто обнуляет все ограничения и таблицы, а затем использует метод ObjectContext.CreateDatabaseScript () для генерации sql, а затем я выполняю его как команду store. Очень похоже на приведенную выше реализацию в вопросе.

Но я также добавил функциональность, чтобы создать хеш из модели и сохранить его в БД, и при повторном запуске он проверяет, совпадает ли текущий хеш модели с тем, который я использовал в БД. Так же, как в реальной реализации кода.

Я не мог заставить его работать со сборкой в ​​context.Database.CompatibleWithModel (true) - но это должно работать так же хорошо, и, учитывая, что это временный обходной путь, все должно быть в порядке.

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'";
    }
}
7 голосов
/ 17 июня 2011

Это самый простой способ получить EF Code First , работающий на AppHarbor !

Использование функции EdmMetadata.TryGetModelHash(context) для проверки того, что модель не соответствует базе данных, и отображение ошибки с новым кодом, который необходимо использовать после запуска сценариев изменения.

PopulateOnly:Создает объекты только тогда, когда база данных пуста

Я думал, что опубликую свою собственную версию Initializer, которую я сейчас использую, на appharbor в для заполнения существующей базы данных .Он также попытается создать, если база данных не существует, и выдает, если обнаружено изменение (извините, автоматического обновления пока нет).Я надеюсь, что кто-то найдет это полезным.

    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";

        }
    }
5 голосов
/ 02 октября 2011

Просто чтобы внести свой вклад в решение @ Luhmann, вот мое, но немного измененное, чтобы правильно сбросить 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'";
    }
}
4 голосов
/ 02 апреля 2011

Я принял несколько иной подход к этой проблеме.Это похоже на то же место, где можно поделиться результатами.

Я хочу создавать только таблицы, которых еще нет в базе данных.Преимущество этого заключается в возможности развертывания новых таблиц без стирания остальной базы данных.

Это также помогает, если в цепочке наследования имеется несколько контекстов данных.Например, если вы разбили свое приложение на разные сборки.Вы можете иметь контекст данных в «основном» модуле, а затем наследовать его в другой сборке для дополнительных модулей.Эта конфигурация работает нормально, но встроенным инициализаторам 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"
          );
      }
    }
  }
}
1 голос
/ 08 марта 2013

Я тоже искал хорошее решение, так как godaddy не позволяет удалять / создавать базы данных и, следовательно, таблицы не создаются.Поскольку более новая версия Entity Framework устарела EDMData, я изменил код Алекса, чтобы посмотреть, существует таблица DropMeToRecreateDatabase или нет, если она не существует, она удаляет все таблицы и создает новые таблицы.

0 голосов
/ 27 октября 2010

Блок питания для генерации базы данных Entity Designer сделает это.Не уверен, работает ли он с Code First, но стоит попробовать.

...