哪个更适合数据库播种:Add 或 AddOrUpdate?
Which is better for database seeding: Add or AddOrUpdate?
不明白为什么到处都推荐在Seed方法中使用AddOrUpdate?
我们已经开发了半年的应用程序,每次我们更新服务器时,AddOrUpdates 都会覆盖用户更改。例如。如果我们调用种子:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
然后用户将样式更改为 "green",然后在下次服务器更新时我们再次将其覆盖为 "red",这让用户非常恼火。
看起来,如果我们将 AddOrUpdate 更改为 Add,我们将保证不会覆盖用户数据。如果我们仍然需要一些特殊情况,我们可以将其单独迁移。与一般的 Configuration.Seed 方法不同,特定的迁移不会 运行 对同一数据库版本进行两次。
我假设 Style
的主键是 Id
。您使用的 The overload of AddOrUpdate
仅检查是否有包含 Id == 1
的记录。如果是这样,它会更新它。就这些了。
这里的问题在于主键是一个代理键,即为了方便查询而存在,但没有商业意义。通常,对于迁移,您希望查找实体的 自然键 。这就是用户识别数据的方式。 S/he 想要绿色样式,而不是 1
.
标识的样式
所以我认为你应该使用 this overload of AddOrUpdate
:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
现在,当不再有红色样式时,将插入一个新样式,覆盖 Id
值(假设它是由数据库生成的)。
从您后来的评论中,我了解到您希望在新数据时添加数据,但在数据存在时不更新它们(通过主键进行比较)。为此,您可以使用我描述的 here 的 AddWhenNew
方法的稍微改编版本。对于你的情况,我会这样做:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
从本地集合中重新获取项目是一件麻烦事,但由于 AddOrUpdate()
中的 bug 是必要的。此错误还导致将原始条目的状态设置为 Unchanged
时出现的错误:它与附加的实例不同。
Add 方法的行为方式具有误导性。即使已经有一行与我们添加的主键相同,它也会将数据插入数据库。它只是创建新的 PrimaryKey 并默默地忽略我们的价值。我应该在问这个问题之前尝试一下,但无论如何,我认为我不是唯一对此感到困惑的人。所以,在我的情况下,Add 比 AddOrUpdate 更糟糕。
我得出的唯一解决方案如下:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}
不明白为什么到处都推荐在Seed方法中使用AddOrUpdate?
我们已经开发了半年的应用程序,每次我们更新服务器时,AddOrUpdates 都会覆盖用户更改。例如。如果我们调用种子:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
然后用户将样式更改为 "green",然后在下次服务器更新时我们再次将其覆盖为 "red",这让用户非常恼火。
看起来,如果我们将 AddOrUpdate 更改为 Add,我们将保证不会覆盖用户数据。如果我们仍然需要一些特殊情况,我们可以将其单独迁移。与一般的 Configuration.Seed 方法不同,特定的迁移不会 运行 对同一数据库版本进行两次。
我假设 Style
的主键是 Id
。您使用的 The overload of AddOrUpdate
仅检查是否有包含 Id == 1
的记录。如果是这样,它会更新它。就这些了。
这里的问题在于主键是一个代理键,即为了方便查询而存在,但没有商业意义。通常,对于迁移,您希望查找实体的 自然键 。这就是用户识别数据的方式。 S/he 想要绿色样式,而不是 1
.
所以我认为你应该使用 this overload of AddOrUpdate
:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
现在,当不再有红色样式时,将插入一个新样式,覆盖 Id
值(假设它是由数据库生成的)。
从您后来的评论中,我了解到您希望在新数据时添加数据,但在数据存在时不更新它们(通过主键进行比较)。为此,您可以使用我描述的 here 的 AddWhenNew
方法的稍微改编版本。对于你的情况,我会这样做:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
从本地集合中重新获取项目是一件麻烦事,但由于 AddOrUpdate()
中的 bug 是必要的。此错误还导致将原始条目的状态设置为 Unchanged
时出现的错误:它与附加的实例不同。
Add 方法的行为方式具有误导性。即使已经有一行与我们添加的主键相同,它也会将数据插入数据库。它只是创建新的 PrimaryKey 并默默地忽略我们的价值。我应该在问这个问题之前尝试一下,但无论如何,我认为我不是唯一对此感到困惑的人。所以,在我的情况下,Add 比 AddOrUpdate 更糟糕。
我得出的唯一解决方案如下:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}