【问题标题】:Rewriting LEFT JOIN sort of LINQ into generic LINQ将 LEFT JOIN 类型的 LINQ 重写为通用 LINQ
【发布时间】:2021-02-07 21:57:39
【问题描述】:

我让这个 LINQ 返回一组记录,这些记录存在于一组中,但另一组中缺少:

var fieldsToDelete = (from field in externalContactsInvolvedFromDb
                                      let item = externalContactsInvolved.FirstOrDefault(i => i.MeetingExternalContactInvolvedId == field.MeetingExternalContactInvolvedId)
                                      where item == null
                                      select field)
                                      .ToList();

externalContactsInvolvedFromDb 和 externalContactsInvolved 是相同类型的对象列表。在这里,我根据主键 (MeetingExternalContactInvolvedId) 从 externalContactsInvolvedFromDb 返回其他集合中不存在的对象列表。

现在,这个逻辑在我的解决方案中的很多地方都重复了,所以我决定将它重写为一个通用方法。不幸的是,我完全被困住了。我想出了这样的东西('U' 是 ExternalContactInvolved 的通用类型):

    string relatedEntityPK = "MeetingExternalContactInvolvedId";
    var x2 = Expression.Parameter(typeof(U), "i");
    var y2 = Expression.Parameter(typeof(U), "meeting");
    var equalExp2 = Expression.Equal(
                        Expression.Property(x2, relatedEntityPK),
                        Expression.Property(y2, relatedEntityPK)
                        );
    var lambda2 = Expression.Lambda<Func<U, bool>>(equalExp2, x2);

用这个我可以写这个:

_context.Set<U>.AsNoTracking().FirstOrDefault(lambda)

但是,它自然不会比较两个数据集,并且会返回无法生成 WHERE 子句的错误。你能帮帮我吗?

【问题讨论】:

  • 您为什么要这样做?即使您设法创建此表达式,维护性也会丢失。
  • 我在整个解决方案的多个地方都使用了这个逻辑,设备之间的唯一区别是使用的类。与其将它复制到 10-20-30 几乎完全相同的方法中,我认为最好使用一种通用方法。我认为维护一个复杂的方法(反正不需要太多调整)比维护30个简单的方法要好。
  • 你觉得通用函数应该是什么样子的?哪些参数?首先将您的查询转换为方法链,然后尝试模拟。无论如何,我认为您必须使用 LEFT JOIN 而不是 FirstOrDefault。

标签: c# linq .net-core entity-framework-core


【解决方案1】:

所以你有两个非常相似类型的序列,并且你想要一个扩展方法来返回第一个序列中不在第二个序列中的所有项目。

通常你会使用 LINQ 的方法,除了这个:

IQueryable<Customer> customers = ...
IQueryable<Customer> maleCustomers = ...
var femaleCustomers = customers.Except(maleCustomers);

但是,您不想检查完全相等,您想给出一些属性并说如果第一个集合的属性的值也是第二个集合中的一个属性,那么不要包含在返回值中。

输入:序列 source1 和 source2,两个 keySelector,为每个 source1 项目和每个 source2 项目选择一个键。输出:所有 source1 项的键不在任何 source2 键中。

为此:进行查询,选择键或所有 source2 项,并返回所有 source1 项,其中键不包含在 source2 键项中。

public static IQueryable<T1> Except<T1, T2, TKey>(
    this IQueryable<T1> source1,
    IQueryable<T2> source2,

   // KeySelectors: what values to use to determine that it should not be in the return?
   Expression<Func<T1, TKey>> key1Selector,
   Expression<Func<T2, TKey>> key2Selector)
{
    IQueryable<TKey> source2KeyItems = source2.Select(source2Item => key2Selector(source2Item));
    return source.Where(source1Item => !source2KeyItems.Contains(key1Selector(source1Item));
}

换句话说:进行查询,从序列 source2 的每个元素中选择 key2Selector 中定义的键。该密钥的类型为 TKey。暂时不要执行查询。

然后,从序列 source1 的每个元素中,使用 key1Selector 选择相同类型 TKey 的键。仅保留源的那些元素,其中键不包含在第一个查询中的键中。

如果你愿意,你可以把它放到一个大的 LINQ 中。你也可以创建一个重载

public static IQueryable<T1> Except<T1, TKey>(
    this IQueryable<T1> source1,
    IQueryable<T2> source2,
   Expression<Func<T1, Key>> keySelector)
{
    return Except(source1, source2, keySelector, keySelector);
}

用法:

这里我根据主键 (MeetingExternalContactInvolvedId) 从 externalContactsInvolvedFromDb 返回其他集合中不存在的对象列表。

IQueryable<Contacts> externalContactsInvolvedFromDb = ...
IQueryable<Contacts otherSet = ...
var externalsThatAreNotInOtherSetBasedOnPrimaryKey = externalContactsInvolvedFromDb.Except(

    otherSet,
    contact => contact.MeetingExternalContactInvolvedId, // use primary key

【讨论】:

  • 谢谢。然而,这两行: IQueryable source2KeyItems = source2.Select(source2Item => key2Selector(source2Item));返回 source1.Where(source1Item => !source2KeyItems.Contains(key1Selector(source1Item)));包含“预期方法名称”错误(key1Selecttor 和 key2Selector 带有下划线)。你知道我该如何克服这个问题吗?
  • 您是否做了任何事情来尝试查找编译器错误?您是否尝试将Expression&lt;Func&lt;T2, Key&gt;&gt; key2Selector 中的Key 更改为TKey。也许你应该熟悉泛型方法的概念,这样你就可以自己检测这些错别字
猜你喜欢
  • 1970-01-01
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
相关资源
最近更新 更多