【问题标题】:Providing a generic key comparison based on a collection of a generic type基于泛型类型的集合提供泛型键比较
【发布时间】:2015-03-15 16:55:54
【问题描述】:

我已经为以下几种类型创建了自己的 InsertOrUpdate() 实现:

public IEnumerable<Genre> InsertOrUpdate(IEnumerable<Genre> genres)
{
    foreach (var genre in genres)
    {
        var existingGenre = _context.Genres.SingleOrDefault(x => x.TmdbId == genre.TmdbId);
        if (existingGenre != null)
        {
            existingGenre.Update(genre);
            yield return existingGenre;
        }
        else
        {
            _context.Genres.Add(genre);
            yield return genre;
        }
    }
    _context.SaveChanges();
}

IEnumerable&lt;T&gt; 的返回类型是必需的,因为它将用于在数据上下文中插入根对象。此方法基本上检索附加的对象(如果存在),如果存在则使用最新值更新它,如果不存在则将其作为新对象插入。之后这个附加对象被返回,因此它可以链接到多对多表中的根对象。

现在的问题是我有几个这样的集合(流派、海报、关键字等),并且每种类型的 ID 设置不同:有时称为TmdbId,有时称为Id,有时称为Iso。使用接口并将它们全部重命名为Id 是一回事,但问题在于它们也是不同的类型:有些是int,有些是string

问题很简单:我如何将它变成更通用的东西以避免这种代码重复?

到目前为止,我一直在玩弄

public IEnumerable<T> InsertOrUpdate<T>(IEnumerable<T> entities, Func<T, bool> idExpression) where T : class 
{
    foreach (var entity in entities)
    {
        var existingEntity = _context.Set<T>().SingleOrDefault(idExpression);
        if (existingEntity != null)
        {
            _context.Entry(existingEntity).CurrentValues.SetValues(entity);
            yield return existingEntity;
        }
        else
        {
            _context.Set<T>().Add(entity);
            yield return entity;
        }
    }
    _context.SaveChanges();
}

但显然这不起作用,因为我无法访问内部 entity 变量。旁注:IDbSet&lt;T&gt;().AddOrUpdate()does not work in my scenario.

【问题讨论】:

  • I have no access to the inner entity variable 是什么意思? entityforeach 的变量
  • @xanatos:我的意思是从调用者的角度来看。理想情况下,我可以传递 x =&gt; x.TmdbId == entity.TmdbId 之类的东西作为参数来指定应该比较的内容,但我不能这样做,因为我无法从该上下文访问实体。
  • Mmmh... 那么,如果您不知道 T 的类型,如何调用InsertOrUpdate? (请注意,您应该使用Expression&lt;Func&lt;T, bool&gt;&gt; 而不是Func&lt;T, bool&gt;)。如果您向我们展示您如何调用InsertOrUpdate,它将变得更加清晰
  • 它目前被这样使用:movie.Genres = new List&lt;Genre&gt;(InsertOrUpdate(movie.Genres)); 并且同样用于所有其他集合。我意识到我可能正在尝试做一些不可能的事情,所以我正在寻找解决此类问题的最佳方法。至于你的旁注:你手边有什么文献可供我阅读吗?
  • 不 :-) 我不读书... 遗憾的是我的注意力太短了:-(

标签: c# linq generics expression-trees func


【解决方案1】:

你可以试试:

public IEnumerable<T> InsertOrUpdate<T>(IEnumerable<T> entities, Func<T, object[]> idExpression) where T : class

var existingEntity = _context.Set<T>().Find(idExpression(entity));

用类似的东西调用

movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => new object[] { x.Id }));

(请注意,返回 IEnumerable&lt;&gt; 的方法是非常危险的...如果您不枚举它,例如

InsertOrUpdate(movie.Genres, x => x.Id);

那么该方法将不会被完全执行,因为它会“按需”延迟执行)

如果你只有单键表,可以改成:

public IEnumerable<T> InsertOrUpdate<T>(IEnumerable<T> entities, Func<T, object> idExpression) where T : class

var existingEntity = _context.Set<T>().Find(new object[] { idExpression(entity) });

movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => x.Id));

【讨论】:

  • 这看起来像我想要做的,但我收到 ArgumentException:指定的参数类型 'System.Func`2[[Models.Movies.Genre, Models, Version=1.0 .0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' 无效。仅支持标量类型,例如 System.Int32、System.Decimal、System.DateTime 和 System.Guid。。请注意,即使添加了一个通用的TKey 类型参数而不是将其作为object 传递,这仍然存在。有什么线索吗?
  • @JeroenVannevel 你确定你复制了 Find 正确吗?看来您正在传递 delegate 而不是调用它!尝试将 idExpression(...) 的结果放入临时变量中(这是我第一次尝试代码时遇到的相同错误:-))
  • 查看错误,您使用的是Func&lt;Genre, int&gt;。这是错误的。它应该是 Func&lt;Genre, object[]&gt;Func&lt;Genre, object&gt;,具体取决于您使用的变体
  • 你说得对,我完全读过了!它完美地工作。我没有注意到idExpression(entity) 而只是idExpression。看来我有很多关于表达式的内容要阅读。
  • Func&lt;Genre, int&gt; 是允许的,因为无论如何它都被Find() 调用封装到params object[] 中。现在我将它用作另一个类型参数,而不是传递object
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-27
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多