我没有找到我想要的东西,但确实找到了对现有的select-detach-update-attach序列的改进。
扩展方法AddOrUpdate(this DbSet)
使您可以执行我想做的事情:如果不存在,请插入;如果发现现有值,则进行更新。我没有意识到很快使用它,因为我真的只看到它seed()
与Migrations结合使用。如果出于任何原因我不应该使用此功能,请告诉我。
需要注意的有用事项:有一个重载可用,它使您可以特别选择应如何确定相等性。在这里,我本可以使用我的,TMDbId
但我选择完全不理会自己的ID,而是在TMDbId上结合使用PK DatabaseGeneratedOption.None
。在适当的时候,我也会在每个子集合上使用这种方法。
有趣的部分来源:
internalSet.InternalContext.Owner.Entry(existing).CurrentValues.SetValues(entity);
这实际上是如何在后台更新数据。
剩下的就是调用AddOrUpdate
每个我希望受到此影响的对象:
public void InsertOrUpdate(Movie movie)
{
_context.Movies.AddOrUpdate(movie);
_context.Languages.AddOrUpdate(movie.SpokenLanguages.ToArray());
// Other objects/collections
_context.SaveChanges();
}
它不像我希望的那样干净,因为我必须手动指定需要更新的对象的每一部分,但是它几乎会达到目标。
相关阅读内容:https : //stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record
更新:
事实证明,我的测试不够严格。使用这种技术后,我注意到添加了新语言后,它并没有连接到电影。在多对多表格中。这是一个已知但似乎优先级较低的问题,据我所知尚未解决。
最后,我决定Update(T)
采用这种方法,在每种类型上都有方法,并遵循以下事件序列:
- 遍历新对象中的集合
- 对于每个集合中的每个条目,在数据库中查找
- 如果存在,请使用
Update()
方法以新值更新它
- 如果不存在,请将其添加到适当的DbSet中
- 返回附加对象,并用附加对象的集合替换根对象中的集合
- 查找并更新根对象
这是大量的手工工作,而且很丑陋,因此将进行更多的重构,但是现在我的测试表明它应该适用于更严格的场景。
进一步清理之后,我现在使用这种方法:
private IEnumerable<T> InsertOrUpdate<T, TKey>(IEnumerable<T> entities, Func<T, TKey> idExpression) where T : class
{
foreach (var entity in entities)
{
var existingEntity = _context.Set<T>().Find(idExpression(entity));
if (existingEntity != null)
{
_context.Entry(existingEntity).CurrentValues.SetValues(entity);
yield return existingEntity;
}
else
{
_context.Set<T>().Add(entity);
yield return entity;
}
}
_context.SaveChanges();
}
这使我可以这样称呼并插入/更新基础集合:
movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => x.TmdbId));
注意,我如何将检索到的值重新分配给原始根对象:现在,它已连接到每个附加对象。更新根对象(电影)的方法相同:
var localMovie = _context.Movies.SingleOrDefault(x => x.TmdbId == movie.TmdbId);
if (localMovie == null)
{
_context.Movies.Add(movie);
}
else
{
_context.Entry(localMovie).CurrentValues.SetValues(movie);
}