我目前正在开发一个使用EF Code First和POCO的项目。到目前为止,我有5个POCO依赖于POCO“用户”。
POCO“User”应该引用我已经存在的MemberShip表“aspnet_Users”(我将它映射到DbContext的OnModelCreating方法中)。
问题在于我想利用“重新创建数据库如果模型更改”功能,如Scott Gu所示:http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - 该功能的基本功能是在看到任何数据库后立即重新创建数据库我的POCO的变化。我想要它做的是重新创建数据库,但以某种方式不删除整个数据库,以便aspnet_Users仍然存在。然而,它似乎是不可能的,因为它要么创建一个全新的数据库,要么用..替换当前的数据库。
所以我的问题是:我是否注定要手动定义我的数据库表,或者我可以以某种方式将我的POCO合并到我当前的数据库中,并且仍然可以使用该功能而无需全部擦除它?
答案 0 :(得分:16)
从CTP5中的EF Code First开始,这是不可能的。 Code First将删除并创建您的数据库,或者它根本不会触及它。我认为在您的情况下,您应该手动创建完整数据库,然后尝试提供与数据库匹配的对象模型。
也就是说,EF团队正在积极研究您正在寻找的功能:更改数据库而不是重新创建它:
的 Code First Database Evolution (aka Migrations) 强>
答案 1 :(得分:7)
我只能在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类,如果需要,可以找到here。
我不打扰博客文章,请阅读documentation。
最后,我仍然建议您在运行初始化之前始终备份数据库。 (例如,如果Seed方法抛出异常,则所有数据都在内存中,因此一旦程序终止,您就有可能丢失数据。)模型更改无论如何都不是事后的操作,因此请务必备份数据
答案 2 :(得分:5)
您可能会考虑使用“断开连接”的外键。您可以单独保留ASPNETDB,只需使用用户密钥(guid)引用数据库中的用户。您可以按如下方式访问登录用户:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
然后在数据库中使用用户密钥作为FK:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
这种方法在体系结构上将您的数据库与ASPNETDB和关联的提供程序分离。但是,在操作上,数据当然会松散地连接,因为ID将在每个DB中。另请注意,没有参考约束,对您来说可能是也可能不是问题。