EF Code First - Воссоздать базу данных, если модель меняется - PullRequest
13 голосов
/ 20 декабря 2010

В настоящее время я работаю над проектом, который использует EF Code First с POCO. У меня есть 5 POCO, которые до сих пор зависят от POCO "Пользователь".

POCO "Пользователь" должен ссылаться на мою уже существующую таблицу MemberShip "aspnet_Users" (с которой я сопоставляю ее в методе OnModelCreating объекта DbContext).

Проблема заключается в том, что я хочу воспользоваться функцией «Воссоздать базу данных, если модель изменится», как показывает Скотт Гу: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - Что делает эта функция в основном, так это воссоздание базы данных, как только она видит любые изменения в моих POCO. То, что я хочу сделать, это воссоздать базу данных, но каким-то образом НЕ удалить всю базу данных, чтобы aspnet_Users был еще жив. Однако это кажется невозможным, поскольку он либо создает совершенно новую базу данных, либо заменяет текущую на ..

Итак, мой вопрос: я обречен определять свои таблицы базы данных вручную, или я могу каким-то образом объединить свои POCO в мою текущую базу данных и по-прежнему использовать эту функцию, не стирая ее все?

Ответы [ 3 ]

16 голосов
/ 20 декабря 2010

Начиная с кода EF Сначала в CTP5 это невозможно.Code First упадет и создаст вашу базу данных или не коснется ее вообще.Я думаю, что в вашем случае вы должны вручную создать свою полную базу данных, а затем попытаться создать объектную модель, соответствующую БД.

Тем не менее, команда EF активно работает над функцией, которую вы ищете: изменение базы данных вместо ее воссоздания:

Code First Database Evolution (также известный как Migrations)

7 голосов
/ 30 сентября 2011

Мне просто удалось сделать это в EF 4.1 со следующими соображениями:

  • CodeFirst
  • DropCreateDatabaseAlways
  • , сохраняя то же самоестрока подключения и имя базы данных

База данных по-прежнему удаляется и создается заново - схема должна отражать изменения вашей модели - но ваши данные остаются нетронутыми.

Вот как: вы считываете свою базу данных в свои объекты POCO в памяти, а затем, после того как объекты POCO успешно поместили ее в память, вы затем позволяете EF отбрасывать и воссоздавать базу данных.Вот пример

public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {

    /// <summary>
    /// Connection from which to ead the data from, to insert into the new database.
    /// Not the same connection instance as the DbContext, but may have the same connection string.
    /// </summary>
    DbConnection connection;
    Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
    public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
        this.connection = connection;           
        this.map = map ?? ReadDataIntoMemory();         
    }

    //read data into memory BEFORE database is dropped
    Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
        Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
        switch (connection.State) {
            case System.Data.ConnectionState.Closed:
                connection.Open();
                break;
        }
        using (this.connection) {
            var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
                            let elementType = p.PropertyType.GetGenericArguments()[0]
                            let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
                            where dbsetType.IsAssignableFrom(p.PropertyType)
                            select new Tuple<PropertyInfo, Type>(p, elementType);

            foreach (var tuple in metaquery) {
                map.Add(tuple, ExecuteReader(tuple));
            }
            this.connection.Close();
            Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
        }       
        return map; 
    }

    protected override void Seed(NorthindDbContext context) {

        foreach (var keyvalue in this.map) {
            foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
                PropertyInfo p = keyvalue.Key.Item1;
                dynamic dbset = p.GetValue(context, null);
                dbset.Add(((dynamic)obj));
            }
        }

        context.SaveChanges();
        base.Seed(context);
    }

    System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
        DbCommand cmd = this.connection.CreateCommand();
        cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
        DbDataReader reader = cmd.ExecuteReader();
        using (reader) {
            ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
                                        .GetConstructors()[0];
            ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
            LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
            System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
            MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
            "ToArray",
            new Type[] { tuple.Item2 },
            Expression.Constant(objreader));
            LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
            var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
            return array;   
        }           
    }
}

Этот пример опирается на класс ObjectReader, который вы можете найти здесь , если вам это нужно.

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

Наконец, я все же рекомендую вам всегда выполнять резервное копирование базы данных перед выполнением инициализации.(Например, если метод Seed выдает исключение, все ваши данные находятся в памяти, поэтому вы рискуете потерять свои данные после завершения программы.) В любом случае изменение модели не совсем запоздалое действие, поэтому обязательно сделайте резервную копию ваших данных.

5 голосов
/ 26 апреля 2011

Одна вещь, которую вы могли бы рассмотреть, это использовать «отключенный» внешний ключ. Вы можете оставить ASPNETDB в покое и просто ссылаться на пользователя в вашей БД, используя ключ пользователя (guid). Вы можете получить доступ к зарегистрированному пользователю следующим образом:

MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);

А затем используйте ключ пользователя в качестве FK в вашей БД:

Guid UserId = (Guid) currentUser.ProviderUserKey ;

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

...