EF Code First - Воссоздать базу данных, если модель меняется
В настоящее время я работаю над проектом, который использует 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 ответа
Начиная с EF Code First в CTP5, это невозможно. Code First упадет и создаст вашу базу данных или не коснется ее вообще. Я думаю, что в вашем случае вы должны вручную создать свою полную базу данных, а затем попытаться создать объектную модель, соответствующую БД.
Тем не менее, команда EF активно работает над функцией, которую вы ищете: изменение базы данных вместо ее воссоздания:
Code First Database Evolution (также известный как Миграции)
Я просто смог сделать это в 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 выдает исключение, все ваши данные находятся в памяти, поэтому вы рискуете потерять данные после завершения программы.) В любом случае изменение модели не совсем запоздалое действие, поэтому обязательно сделайте резервную копию ваших данных,
Одна вещь, которую вы могли бы рассмотреть, это использовать "отключенный" внешний ключ. Вы можете оставить ASPNETDB в покое и просто ссылаться на пользователя в вашей БД, используя ключ пользователя (guid). Вы можете получить доступ к зарегистрированному пользователю следующим образом:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
А затем используйте ключ пользователя в качестве FK в вашей БД:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
Этот подход архитектурно разделяет вашу БД с ASPNETDB и связанным провайдером. Однако в оперативном отношении данные, конечно, будут слабо связаны, поскольку идентификаторы будут в каждой БД. Обратите внимание, что не будет никаких ограничений по ссылкам, что может или не может быть проблемой для вас.