EF Code First - воссоздать базу данных, если изменения модели
В настоящее время я работаю над проектом, который использует EF Code First с POCOs. У меня есть 5 POCOs, которые до сих пор зависят от пользователя "POCO".
Пользователь POCO "Пользователь" должен ссылаться на мою уже существующую таблицу MemberShip "aspnet_Users" (которую я сопоставляю с ней в методе OnModelCreating для DbContext).
Проблема заключается в том, что я хочу использовать функцию "Восстановить базу данных, если меняет модель", как показывает Скотт Гу: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - В основном эта функция заключается в том, чтобы воссоздать базу данных, как только она увидит изменения в моих POCOs. Я хочу, чтобы это было, чтобы восстановить базу данных, но каким-то образом НЕ удалять всю базу данных, чтобы aspnet_Users все еще был жив. Однако это кажется невозможным, поскольку он либо создает целую новую базу данных, либо заменяет текущую.
Итак, мой вопрос: я обречен определять вручную таблицы базы данных или могу ли я каким-то образом объединить мои POCO в свою текущую базу данных и все еще использовать эту функцию, не протирая все это?
Ответы
Ответ 1
С кодом EF Сначала в CTP5 это невозможно. Код Сначала упадет и создаст вашу базу данных или вообще не коснется ее. Я думаю, что в вашем случае вы должны вручную создать свою полную базу данных, а затем попытаться создать объектную модель, которая соответствует БД.
Тем не менее, команда EF активно работает над функцией, которую вы ищете: изменение базы данных вместо ее воссоздания:
Code First Database Evolution (aka Migrations)
Ответ 2
Я просто смог сделать это в EF 4.1 со следующими соображениями:
База данных по-прежнему удаляется и воссоздается - она должна быть для схемы, отражающей ваши изменения модели, но ваши данные остаются нетронутыми.
Вот как: вы читаете свою базу данных в своих объектах 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 генерирует исключение, все ваши данные хранятся в памяти, поэтому вы рискуете потерять свои данные после завершения программы.) Изменение модели в любом случае не является окончательным действием, поэтому обязательно верните данные вверх.
Ответ 3
Одна вещь, которую вы можете учесть, - использовать "отключенный" внешний ключ. Вы можете оставить ASPNETDB самостоятельно и просто ссылаться на пользователя в своей БД, используя ключ пользователя (guid). Вы можете получить доступ к зарегистрированному пользователю следующим образом:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
И затем используйте ключ пользователя как FK в вашей БД:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
Этот подход отделяет вашу БД с ASPNETDB и ассоциированным провайдером в архитектуре. Однако, оперативно, данные, конечно, будут слабо связаны, так как идентификаторы будут в каждой БД. Обратите внимание, что не будет никаких ссылочных ограничений, которые могут или не могут быть проблемой для вас.