【问题标题】:list compare and replace on matching condition linq C#列表比较和替换匹配条件linq C#
【发布时间】:2020-12-11 21:43:45
【问题描述】:
public class UserValues
{
    public string UserId { get; set; }
    public int FieldId { get; set; }
    public string FieldValue { get; set; } 
}

public class LookupMeta
{
    public int FieldId { get; set; }
    public int Id { get; set; }
    public int FieldValueId { get; set; }
    public string Title { get; set; }
}

从 DB 中读取后,我将其保存在 2 个不同的列表中。

现在我想将这两个列表与

  • FieldId == FieldId
  • FieldValue 等于Id

然后将FieldValueuservalues替换为FieldValueIdlookupMeta

UserValues
    .Where(x => LookupMeta.Any(y => 
        y.FieldId == x.FieldId && 
        y.FieldValueId.Equals(x.FieldValue)))
    .Select(x => x.FieldValue.Replace(x.FieldValue, ???))
        

我也在看这个链接。我很震惊C# LINQ code for two list compare and replace

在List里面做这样的做法好还是有其他优化的方式?

【问题讨论】:

  • 首先y.FieldValueId.Equals(x.FieldValue) 这应该做什么?您将intstring 值进行比较,这将始终返回false。您是否希望将该字符串值解析为 int 并与之进行比较?最后,这种方式绝对不是最快的方式,但 imo 首先尝试使这项工作适用于单个记录,并且只有当您的逻辑正常工作时,才尝试使其与 2 个列表一起工作。

标签: c# list linq


【解决方案1】:

根据pwilcox's answer 上留下的评论,似乎 OP 正在寻找一个解决方案,其中还包括不匹配的行。这意味着我们正在寻找左外连接,而不是使用内连接。

在 Linq 的世界中,这可以通过 GroupJoinSelectManySelect 运算符的组合来实现。

为了能够连接两个不同的列,我们必须引入一个中间类来识别GroupJoin 的类型。所以,我创建了以下类:

internal class IntermediateKey
{
    public int Id { get; set; }
    public string Value { get; set; }
}

我们还必须为这个类定义一个比较器才能找到匹配的数据:

internal class IntermediateKeyComparer : IEqualityComparer<IntermediateKey>
{
    public bool Equals(IntermediateKey x, IntermediateKey y)
    {
        return x.Id == y.Id && x.Value == y.Value;
    }

    public int GetHashCode(IntermediateKey obj)
    {
        return obj.Id.GetHashCode() + obj.Value.GetHashCode();
    }
}

请记住,此实现非常简单。正确的实现方式见this thread

现在可以这样定义我们的查询:

var comparer = new IntermediateKeyComparer();
var result = userValues
    .GroupJoin(
        lookupMetas,
        uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
        lm => new IntermediateKey  { Id =  lm.FieldId, Value = lm.Id.ToString() },
        (uv, lm) => new { Value = uv, Lookups = lm},
        comparer)
    .SelectMany(
        pair => pair.Lookups.DefaultIfEmpty(),
        (paired, meta) => new { Value = paired.Value, Lookup = meta})
    .Select(res =>
    {
        res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
        return res.Value;
    });
  • 我们定义userValues 应该在lookupMetas 上保持外部连接
    • 如果uvFieldIdlmFieldId 匹配
    • 如果uvFieldValue匹配lmId的字符串表示
  • 对于SelectMany,我们选择匹配的LookupMeta 实体或null
  • 对于Select,我们仅在存在相关LookupMeta 时更新UserValueFieldValue 属性,否则我们使用其原始值。

现在让我们看看它如何处理一些示例数据:

static void Main(string[] args)
{
    var userValues = new List<UserValue>
    {
        new UserValue { FieldId = 1, FieldValue = "2"},
        new UserValue { FieldId = 2, FieldValue = "3"},
        new UserValue { FieldId = 4, FieldValue = "5"}
    };

    var lookupMetas = new List<LookupMeta>
    {
        new LookupMeta { FieldId = 1, Id = 2, FieldValueId = 20 },
        new LookupMeta { FieldId = 2, Id = 3, FieldValueId = 30 },
        new LookupMeta { FieldId = 3, Id = 4, FieldValueId = 40 },
    };

    var comparer = new IntermediateKeyComparer();
    var result = userValues
        .GroupJoin(
            lookupMetas,
            uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
            lm => new IntermediateKey  { Id =  lm.FieldId, Value = lm.Id.ToString() },
            (uv, lm) => new { Value = uv, Lookups = lm},
            comparer)
        .SelectMany(
            pair => pair.Lookups.DefaultIfEmpty(),
            (x, meta) => new { Value = x.Value, Lookup = meta})
        .Select(res =>
        {
            res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
            return res.Value;
        });

    foreach (var maybeUpdatedUserValue in result)
    {
        Console.WriteLine($"{maybeUpdatedUserValue.FieldId}: {maybeUpdatedUserValue.FieldValue}");
    }
}

输出将是:

1: 20
2: 30
4: 5

所以,如您所见,最后一个 UserValue 没有匹配的 LookupMeta,这就是它的 FieldValue 保持不变的原因。

【讨论】:

  • 非常感谢您的快速回复并与解释分享,以便我也可以学习。我会试试这个并更新这个线程。祝你有美好的一天。
  • 非常感谢。我现在可以理解并且它正在工作。
【解决方案2】:

如果我正确地跟随你,那么 LINQ 中的 .Join() 方法可能对你有用。在这里,我用它来完成我认为你所追求的。

UserValues
.Join(
    LookupMeta, 
    uv => new { uv.FieldId, uv.FieldValue },
    lm => new { lm.FieldId, lm.FieldValueId },
    (uv,lm) => {
        uv.FieldValue = lm.FieldValueId;
        return uv;
    }
);

方法中的第二行和第三行从源表构建匿名对象。匹配这些值以建立链接。

最后一行将连接的条目作为输入,然后给出您的输出。在您的情况下,我只返回 UserValues 条目。但在此之前,我将其“FieldValue”属性更改为 LookupMeta 条目的“FieldValueId”属性。

您有一些不一致的地方。例如,您在段落中谈论将 FieldValue 与 Id 匹配,但在代码中您将 FieldValue 与 FieldValueId 匹配。此外,您在一个比较中使用==,在另一个比较中使用.Equals()。这里没有错误的答案。我只是不知道你的底层对象。所以你可能需要稍微修改我的代码才能得到你想要的。但它显示了我希望对你有用的一般策略。

【讨论】:

  • 感谢您的快速回复和解释。我得到了查询,但它没有给我正确的答案。如果我没有更早地解释这一点,我很抱歉。 UserValues 有 1001 行,查找元数据有 11 行。在此之后,我想要 1001 行并替换 UserValues 行中适用的值。目前,它不包括不匹配的行。非常感谢任何帮助。
  • Rocky3151,@PeterCsala 的回答是正确的。您正在寻找 LINQ 中的左连接。没有直接的左连接方法,但可以做到。看来他已经为你完成了。
  • 需要考虑的是,使用 SQL 进行左连接然后将其导入 C# 的可能性。
  • @pwilox 感谢您的建议。不幸的是,我必须写入动态 SQL,因为表名本身是动态的,并且使用 unpivot 来获取上述列表之一。已经很复杂了。感谢您的回复,我将尝试以下方法并更新线程
  • @pwilox 非常感谢。我现在可以理解整个线程及其按预期工作。祝你有美好的一天。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
  • 2018-05-14
  • 1970-01-01
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多