简体   繁体   English

使用linq在一个属性c#上过滤两个列表

[英]Filter two lists on one property c# using linq

I have two objects namely Card and Transaction : 我有两个对象,即交易

Card:
public string CardID {get; set;}
public string TransactionRef {get; set;}

Transaction:
public string TxnID {get; set;}
public string TxnDetails {get; set;}

Note: The TransactionRef is of the format Date|TxnID 注意: TransactionRef的格式为Date|TxnID

I also have a list of the two objects List<Card> cardDetails and List<Transaction> transDetails 我还列出了两个对象List<Card> cardDetailsList<Transaction> transDetails

cardDetails:
{CardID = '1', TransactionRef = '20150824|Guid1'}
{CardID = '2', TransactionRef = '20150824|Guid2'}
{CardID = '3', TransactionRef = '20150824|Guid3'}

transDetails:
{TxnID = '23', TxnDetails = 'Guid1'}
{TxnID = '24', TxnDetails = 'Guid2'}

I want to filter cardDetails using transDetails based on TxnDetails so that it filters out the items which do not contain the TxnDetails from the 2nd list. 我想使用基于TxnDetails的transDetails过滤cardDetails,以便过滤掉第二个列表中不包含TxnDetails的项目。

This should be the output: 这应该是输出:

cardDetails:
 {CardID = '3', TransactionRef = '20150824|Guid3'}

I have tried like this using linq: 我试过这样使用linq:

  cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails) == false)).ToList();

but it always returns the list as blank. 但它总是将列表返回为空白。 I have tried many variants of this query without success. 我已尝试此查询的许多变体但没有成功。 I know this question has been asked before and after searching for them and trying out their solutions I am still unable to get it right. 我知道在搜索它们之前和之后已经问过这个问题并尝试了解决方案我仍然无法做到正确。

Can anyone suggest what is wrong with my query? 任何人都可以建议我的查询有什么问题吗?

Note: One thing I forgot to mention is that these lists can contains 1000s of records. 注意:我忘了提到的一件事是这些列表可以包含1000条记录。 So performance is also important. 所以表现也很重要。

This should do it 这应该做到这一点

var cards = 
    from card in cardDetails
    let txnDetails = GetTxnDetails(card)
    where ! transDetails.Any(t => t.TxnDetails == txnDetails)
    select card;


static string GetTxnDetails(Card card)
{
    return card.TransactionRef.Split('|')[1];
}

Fiddle: https://dotnetfiddle.net/b9ylFe 小提琴: https//dotnetfiddle.net/b9ylFe


One way to optimize this a bit would be to store all the possible transaction details in a hash set upfront. 优化这一点的一种方法是将所有可能的事务细节存储在哈希集中。 The lookup should then be pretty close to O(1) (assuming a fair hashcode distributation) instead of O(n) - bringing the overall complexity of the algorithm from O(n * k) down to O(n + k). 然后,查找应非常接近O(1)(假设公平的哈希码分布)而不是O(n) - 使算法的总体复杂度从O(n * k)下降到O(n + k)。

var allTxnDetails = new HashSet<string>(transDetails.Select(t => t.TxnDetails));

var cards = 
    from card in cardDetails
    let txnDetails = GetTxnDetails(card)
    where ! allTxnDetails.Contains(txnDetails)
    select card;

Fiddle: https://dotnetfiddle.net/hTYCbj 小提琴: https//dotnetfiddle.net/hTYCbj

This query should do the trick: 这个查询应该做的伎俩:

// Get all card details whose transactionrefs don't contain txndetails from the second list
cardDetails.Where(cd => transDetails.All(ts => !cd.TransactionRef.EndsWith(ts.TxnDetails)))
    .ToList();

But is there any specific reason why you are combining two pieces of data in one field? 但是,为什么要在一个字段中组合两个数据? I suggest breaking the TransactionRef field in your Card class into two fields: TransactionDate and TransactionID to avoid string manipulation in queries. 我建议将Card类中的TransactionRef字段分成两个字段: TransactionDateTransactionID以避免查询中的字符串操作。

How about this? 这个怎么样?

var results = cardDetails.Where(
    card => !transDetails.Any(
        trans => card.TransactionRef.EndsWith("|" + trans.TxnDetails)));

Full demo: 完整演示:

using System;
using System.Linq;

namespace Demo
{
    class Card
    {
        public string CardID;
        public string TransactionRef;
    }

    class Transaction
    {
        public string TxnID;
        public string TxnDetails;
    }

    internal class Program
    {
        private static void Main()
        {
            var cardDetails = new[]
            {
                new Card {CardID = "1", TransactionRef = "20150824|Guid1"},
                new Card {CardID = "2", TransactionRef = "20150824|Guid2"},
                new Card {CardID = "3", TransactionRef = "20150824|Guid3"}
            };

            var transDetails = new[]
            {
                new Transaction {TxnID = "23", TxnDetails = "Guid1"},
                new Transaction {TxnID = "24", TxnDetails = "Guid2"}
            };

            var results = cardDetails.Where(card => !transDetails.Any(trans => card.TransactionRef.EndsWith("|" + trans.TxnDetails)));

            foreach (var item in results)
                Console.WriteLine(item.CardID + ": " + item.TransactionRef);    
        }
    }
}

it's just a parenthesis problem, the == false should come after )) not the first closing one. 它只是一个括号问题, == false应该在之后))而不是第一个结束问题。

cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails)) == false).ToList();

Cause with your actual code, you just do the opposite of what you want ! 因为你的实际代码,你只是做你想要的反面!

you can also do 你也可以

cardDetails = cardDetails.Where(x => !transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails))).ToList();

or any improvment suggested, but your code is basically really close from correct ;) 或任何改进建议,但你的代码基本上非常接近正确;)

Using method chainining syntax for LINQ: 使用LINQ的方法链接语法:

List<Card> result = cardDetails.Where(
    card => !transDetails.Exists(
         tran => tran.TxnDetails == card.TransactionRef.Split('|')[1]
)).ToList();

What's wrong with your query ? 您的查询有什么问题?

 cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails) == false)).ToList();

This is what you've written: 这就是你写的:

Find me all Cards that satisfy this condition: Is there any Transaction in my list of transactions that this particular Transaction has TxnDetails that cannot be found in TxnDetails of this particular Card ? 找到满足这个条件的所有卡片:在我的交易列表中是否有任何交易,这个特定交易有TxnDetails,在这个特定卡的TxnDetails中找不到?

I can see problem here: 我在这里可以看到问题:

If any transaction has another TxnId than a Card (chances are quite high), return this Card. 如果任何交易有另一个TxnId而不是卡(可能性很高),请退回此卡。

So, basically you should get all cards from your query if your Transaction List has at least 2 different transaction ids in it 因此,如果您的交易清单中至少有2个不同的交易ID,基本上您应该从查询中获取所有卡

If performance is important, I suggest you should first give class Card a property that returns the part after the '|' 如果性能很重要,我建议你首先给类卡一个属性,返回'|'之后的部分 character. 字符。 Depending on how often you want to do this query compared with how often you construct a Card it might even be wist to let the constructor separate the transactionRef into a part before the '|' 根据您想要执行此查询的频率与构建卡的频率相比,甚至可能让构造函数将transactionRef分隔为“|”之前的部分 and a part after the '|'. 和'|'之后的一部分。

Whichever method you choose is not important for the query. 无论您选择哪种方法对查询都不重要。 Let's suppose class Card has a property: 我们假设类卡有一个属性:

string Guid {get {return ...;}

I understand that you want a sequence of all Cards from the sequence cardDetails that don't have a Guid that equals any of the TxnDetails of the Transactions in the sequence of transDetails. 我知道你想要一个序列cardDetails中所有卡片的序列,这些卡片没有Guid,它等于transDetails序列中Transactions的任何TxnDetails。

Or in other words: if you would make a sequence of all used guids in TxnDetails, you want all Cards in CardDetails that have a guid that is not in the sequence of all used guids. 或者换句话说:如果你要在TxnDetails中制作所有使用的guid的序列,你希望CardDetails中的所有卡片的guid都不是所有使用guid的序列。

You could use Any() for this, but that would mean that you have to search the transDetails sequence for every card you'd want to check. 您可以使用Any(),但这意味着您必须为每个要检查的卡搜索transDetails序列。

Whenever you have to check whetherany specific item is in a sequence or not, it is better to convert the sequence once to a Dictionary or a HashSet. 每当您必须检查特定项目是否在序列中时,最好将序列一次转换为Dictionary或HashSet。 Whichever you create depends on whether you only need the key or the element that has the key. 无论您创建哪个,取决于您是否只需要密钥或具有密钥的元素。 Create the dictionary / hashset only once, and search very fast for the item with the key. 仅创建一次字典/ hashset,并使用键快速搜索项目。

In our case we only want a sequence with used guids, it doesn't matter in which Transaction it is used. 在我们的例子中,我们只需要一个带有guid的序列,它与使用哪个Transaction无关。

var usedGuids = transDetails.Select(transDetail => transDetail.TxnDetails).Distinct();
var hashedGuids = new HashSet(usedGuids);

(I made two statement to make it easier to understand what is done) (我做了两个声明,以便更容易理解做了什么)

Now, whenever I have a GUID I can check very fast if it is used or not: 现在,每当我有一个GUID,我可以非常快地检查它是否被使用:

bool guidIsUsed = usedGuids.Contains(myGuid);

So your sequence of Cards in cardDetails with a GUID that is not in transDetails is: 因此,cardDetails中具有不在transDetails中的GUID的卡序列是:

var hashedGuids = new HashSet(transDetails.Select(transDetail => transDetail.TxnDetails).Distinct());
var requestedCards = cardDetails.Where(card => !hashedGuids.Contains(card.Guid));

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

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