我可以使用分离但相等的对象更新附加对象吗?


10

我从外部API检索电影数据。在第一阶段,我将抓取每部电影并将其插入我自己的数据库中。在第二阶段中,我将使用API​​的“更改” API定期更新数据库,我可以查询该API以查看哪些电影的信息已更改。

我的ORM层是实体框架。Movie类如下所示:

class Movie
{
    public virtual ICollection<Language> SpokenLanguages { get; set; }
    public virtual ICollection<Genre> Genres { get; set; }
    public virtual ICollection<Keyword> Keywords { get; set; }
}

当我有一部需要更新的电影时,就会出现问题:我的数据库会认为正在跟踪的对象以及从update API调用接收到的新对象都是不同的对象,而无视.Equals()

这引起了一个问题,因为当我现在尝试使用更新的影片来更新数据库时,它将插入数据库而不是更新现有的影片。

我以前在语言方面遇到过这个问题,我的解决方案是搜索附加的语言对象,将它们与上下文分离,将其PK移至更新的对象,然后将其附加到上下文。当SaveChanges()正在执行,它将基本上取代它。

这是一种很臭的方法,因为如果我继续对Movie对象使用这种方法,则意味着我必须分离电影,语言,流派和关键字,在数据库中查找每个人,传输其ID并插入新对象。

有没有办法更优雅地做到这一点?理想情况下,我只想将更新的影片传递给上下文,并让该影片根据该Equals()方法选择正确的影片进行更新,更新其所有字段并针对每个复杂对象:根据其自己的Equals()方法再次使用现有记录,并插入它尚不存在。

我可以通过.Update()在每个复杂对象上提供方法来跳过分离/附加,可以结合使用这些方法来检索所有附加对象,但这仍然需要我检索每个现有对象然后进行更新。


为什么不从API数据中更新跟踪的实体并保存更改而不替换/分离/匹配/附加实体呢?
Si-N

@ Si-N:您能扩展一下该怎么做吗?
Jeroen Vannevel 2015年

好的,现在您添加了最后一段更有意义,您在尝试避免在更新实体之前检索它们吗?电影课上没有什么可能是您的PK?您如何将电影从外部api匹配到您的实体?无论如何,您都可以在一个db调用中检索需要更新的所有实体,这不是应该不会对性能造成重大影响的更简单的解决方案(除非您谈论的是要更新的大量电影)?
Si-N

我的电影类具有PK,id并且使用字段将来自外部API的电影与本地电影进行匹配tmdbid。我无法在一次调用中检索所有需要更新的实体,因为它涉及电影,体裁,语言,关键字等。每个实体都有一个PK,可能已经存在于数据库中。
Jeroen Vannevel 2015年

Answers:


8

我没有找到我想要的东西,但确实找到了对现有的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);
}

您如何处理1-M关系中的删除?例如,1-电影可以有多种语言;如果其中一种语言被删除,您的代码是否将其删除?您的解决方案似乎只能插入和/或更新(但不能删除?)
joedotnot

0

既然你是在处理不同的领域idtmbid,我建议更新API来做出这样的流派,语言文字,关键字等资讯的所有单一和独立的指数...,然后对指数的调用和检查信息,而不是收集Movie类中有关特定对象的完整信息。


1
我在这里不遵循思路。你能扩大吗?请注意,外部API完全不受我的控制。
Jeroen Vannevel 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.