简体   繁体   English

Linq获取自定义对象的列表,其中字典属性中包含的某个值等于一个特定值

[英]Linq to get list of custom objects where a certain value contained within dictionary property equals a specific value

I am struggling to solve this issue and have searched multiple ways and cannot seem to find an answer. 我正在努力解决此问题,并且已经搜索了多种方法,但似乎找不到答案。 I inherited this app from someone else and need to add a couple features to the app. 我从其他人那里继承了此应用,因此需要向该应用添加一些功能。 I have not worked much with dictionaries and linq before, so I have been searching and trying to gain knowledge to do what I need to do. 之前我与字典和linq的合作并不多,所以我一直在搜索并尝试获取知识来做我需要做的事情。

There is a class with the following properties(removed some properties not necessary for this discussion): 有一个具有以下属性的类(删除了一些本讨论不需要的属性):

class EmailRecord
{
    public Dictionary<string, List<string>> emails = new Dictionary<string, List<string>>();
    public string RecordID { get; set; }

[followed by additional properties and constructors...] [其次是其他属性和构造函数...]

When the objects are created, the emails Property would have a template string in the key, and a list of strings containing email addresses in the values. 创建对象时,电子邮件属性将在键中包含模板字符串,并在值中包含包含电子邮件地址的字符串列表。 For my purposes, I do not need to know what is in the key. 就我的目的而言,我不需要知道密钥中的内容。

I have a list of EmailRecord objects called allRecords. 我有一个称为allRecords的EmailRecord对象列表。 I need to query allRecords to get a list of all EmailRecord objects where the emails dictionary property's list of values contains a specific email address I have stored in a variable called recipientEmail. 我需要查询allRecords以获得所有EmailRecord对象的列表,其中emails字典属性的值列表包含一个特定的电子邮件地址,该地址已存储在一个名为receiveEmail的变量中。 The key doesn't matter, and it doesn't matter how many times the email shows up. 密钥无关紧要,电子邮件显示多少次也无关紧要。 I just need the instance of the object included in the results if the email shows up anywhere in the values of the emails property. 如果电子邮件出现在emails属性的值中的任何地方,我只需要结果中包含的对象的实例。 In an instance of EmailRecord, the emails dictionary property may have two keys and within each of those keys, multiple emails in a list of strings for the value. 在EmailRecord的实例中,电子邮件词典属性可以具有两个键,并且在每个键内,该值的字符串列表中包含多个电子邮件。 I don't need to limit to a specific key, I just need to know if an email exists anywhere within the list of email strings anywhere in that dictionary. 我不需要限制于特定的键,我只需要知道在该字典中任何位置的电子邮件字符串列表中是否存在任何电子邮件。

I've tried a few things, with the latest being this (which doesn't work): 我已经尝试了一些方法,最近的方法是这样的(不起作用):

var results = EmailRecords
    .SelectMany(x => x.emails)
    .Where(x => x.Value.Contains(recipientEmail));

The above just seems to be returning the dictionary property, not the entire object. 上面的代码似乎只是在返回dictionary属性,而不是整个对象。

I want to be able to loop through the results with something like this: 我希望能够像这样遍历结果:

foreach (EmailRecord foundRecord in results) {
    ...do work here
}

Any thoughts or suggestions to assist me as I am trying to learn Linq? 在尝试学习Linq时有什么想法或建议可以帮助我吗? Thank you in advance for any help you can provide. 预先感谢您提供的任何帮助。

If you want to loop through EmailRecord objects which one of its emails property values contains recipientEmail , then you need to have a list of EmailRecord first. 如果要遍历EmailRecord对象,该对象的emails属性之一包含recipientEmail ,则首先需要有一个EmailRecord列表。 Then search throught them. 然后搜索他们。 following should do the trick. 以下应该可以解决问题。

List<EmailRecord> EmailRecords = new List<EmailRecord>();

//Fill the EmailRecords somewhere

foreach (KeyValuePair<string, List<string>> emailfoundRecord in 
                  EmailRecords.emails.Where(x => x.Value.Contains(recipientEmail)))
 {
   //do work here       
 }

When you call EmailRecords.SelectMany(x => x.Emails) what you get back is an IEnumerable<KeyValuePair<string, List<string>>> or similar. 当您调用EmailRecords.SelectMany(x => x.Emails) ,您得到的是IEnumerable<KeyValuePair<string, List<string>>>或类似名称。 Obviously this is not what you're after for your result since it strips away all that other information. 显然,这不是您追求的结果,因为它剥夺了所有其他信息。

With LINQ the first thing to consider at each stage is what you are expecting to get out of the query. 使用LINQ,在每个阶段要考虑的第一件事是您期望从查询中获得什么。 In this case the query results should be an enumeration of EmailRecord instances which is also what we're feeding in. Filtering that list is most simply done with the Where method, so that's where you should do all the work 在这种情况下,查询结果应该是EmailRecord实例的枚举,这也是我们要输入的内容。最简单的过滤列表是使用Where方法,因此您应该在其中进行所有工作

Next decide on your filter criteria and write the filter predicate to suit. 接下来确定您的过滤条件,并写出适合的过滤谓词。 For any given EmailRecord we want to find out if any of the dictionary entries contains a particular email address. 对于任何给定的EmailRecord我们想确定是否有任何词典条目包含特定的电子邮件地址。 Since the dictionary values are lists we'll use Contains to do the actual comparison, and Any to test the dictionary itself. 由于字典值是列表,因此我们将使用Contains进行实际比较,并使用Any测试字典本身。

Which looks like this: 看起来像这样:

var filtered = EmailRecords.Where(e => 
    e.Emails.Any(kv => 
        kv.Value.Contains(recipientEmail)
    )
);

This works because a dictionary is also an enumerable, with each entry in the enumeration being a key/value pair. 这是可行的,因为字典也是可枚举的,枚举中的每个条目都是键/值对。

Using Any will stop when it finds a single matching entry instead of continuing to the end of the Emails dictionary for every EmailRecord instance. 当找到单个匹配条目时,将停止使用Any而不是针对每个EmailRecord实例继续到Emails字典的末尾。 If there are a lot of emails and you're expecting a high number of selections then this might save some time. 如果有很多电子邮件,并且您期望有大量选择,那么这可能会节省一些时间。 Probably not however, since generally this sort of structure doesn't have a lot of duplicate email addresses in it. 但是,可能不是,因为通常这种结构中没有很多重复的电子邮件地址。

Depending on how often you want to do this however it might be quicker to build a lookup and query that. 取决于您要执行此操作的频率,但是构建查询和查询可能更快。 Assuming that your EmailRecords list changes infrequently and you are doing a lot of this sort of lookup, you could get a large speedup. 假设您的EmailRecords列表不经常更改,并且您正在进行大量此类查找,则可以大大提高速度。

I'll use a Dictionary<string, EmailRecord[]> for the lookup because it's (fairly) simple to build once we get a list of all of the pairs of email address and EmailRecord objects: 我将使用Dictionary<string, EmailRecord[]>进行查找,因为一旦我们获得了所有成对的电子邮件地址和EmailRecord对象的列表,它的构建(相当)容易:

var emailReferences = EmailRecords.SelectMany(e => 
    e.Emails.SelectMany(kv => 
        kv.Value.Select(v => 
            new { address = v, record = e }
        )
    )
);

var lookup = 
    emailReferences
    .GroupBy(i => i.address, i => i.record)
    .ToDictionary(g => g.Key, g => g.ToArray());
;

From this you will be able to locate an email address and get its referencing EmailRecord instances fairly simply and quickly: 通过此操作,您将可以非常简单,快速地找到一个电子邮件地址并获取其引用EmailRecord实例:

EmailRecord[] filtered = null;
lookup.TryGetValue(recipientEmail, out filtered);

This will be faster per lookup than the LINQ equivalent above, but the setup could consume a fair amount of time and memory for large lists. 每次查找的速度将比上面的LINQ更快,但是对于大型列表,该设置可能会消耗大量的时间和内存。 If you have small or frequently changing lists (since the lookup has to be regenerated or at least invalidated at each change) then this won't improve your program's speed. 如果您的列表很小或经常更改(由于每次更改都必须重新生成查找或至少使查找无效),则这不会提高程序的速度。


As a completely unsolicited aside, here's an extension method I use when dealing with dictionaries that have List<> as the value: 顺便说一句,这是我在处理以List<>为值的字典时使用的扩展方法:

public static partial class extensions
{
    public static Dictionary<TKey, List<TElem>> Add<TKey, TElem>(this Dictionary<TKey, List<TElem>> dict, TKey key, TElem value)
    {
        List<TElem> list;
        if (dict.ContainsKey(key))
            list = dict[key];
        else
            dict[key] = list = new List<TElem>();

        list.Add(value);
        return dict;
    }
}

It helps make your adds simpler and easier to read. 它有助于使您的添加内容更简单易读。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM