C# 使用linq筛选一个属性c上的两个列表

C# 使用linq筛选一个属性c上的两个列表,c#,linq,C#,Linq,我有两个对象,即卡和交易: 注:TransactionRef的格式为Date | TxnID 我还有一个两个对象的列表list cardDetails和list transDetails 我想使用基于TxnDetails的transDetails来过滤cardDetails,以便从第二个列表中过滤出不包含TxnDetails的项目 这应该是输出: cardDetails: {CardID = '3', TransactionRef = '20150824|Guid3'} 我使用linq尝试过

我有两个对象,即卡和交易:

注:TransactionRef的格式为Date | TxnID

我还有一个两个对象的列表list cardDetails和list transDetails

我想使用基于TxnDetails的transDetails来过滤cardDetails,以便从第二个列表中过滤出不包含TxnDetails的项目

这应该是输出:

cardDetails:
 {CardID = '3', TransactionRef = '20150824|Guid3'}
我使用linq尝试过这样的方法:

  cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails) == false)).ToList();
但它总是将列表返回为空。我已经尝试了这个查询的许多变体,但都没有成功。我知道在寻找他们并尝试他们的解决方案之前和之后都有人问过这个问题,但我仍然无法正确回答

有人能告诉我我的问题出在哪里吗

注意:我忘了提到的一点是,这些列表可以包含1000条记录。因此,性能也很重要。

这应该可以做到

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];
}
小提琴:

一种稍微优化这一点的方法是将所有可能的事务细节预先存储在散列设置中。然后,如果采用公平的hashcode分布,而不是On,那么查找应该非常接近O1,从而将算法的总体复杂度从On*k降低到On+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;
小提琴:这个怎么样

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

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);    
        }
    }
}

这只是一个括号问题,==false应该在第一个结束符之后出现

cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails)) == false).ToList();
因为对于你的实际代码,你只是做了与你想要的相反的事情

你也可以

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

或者任何建议的改进,但是您的代码基本上是非常接近正确的

此查询应该可以实现以下功能:

// 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();

但是,为什么要在一个字段中组合两段数据,有什么具体原因吗?我建议将Card类中的TransactionRef字段分为两个字段:TransactionDate和TransactionID,以避免查询中的字符串操纵。

如果性能很重要,我建议您首先为Card类提供一个返回“|”字符后部分的属性。根据您希望执行此查询的频率与构造卡片的频率,甚至可以让构造函数将transactionRef分为“|”之前的一部分和“|”之后的一部分

选择哪种方法对查询都不重要。让我们假设类卡片有一个属性:

string Guid {get {return ...;}
我知道您需要序列cardDetails中的所有卡的序列,这些卡的Guid不等于transDetails序列中交易的任何TXN细节

或者换句话说:如果要在TxnDetails中对所有使用的guid进行排序,则需要CardDetails中的所有卡的guid不在所有使用的guid的顺序中

您可以使用任何一种,但这意味着您必须搜索transDetails序列以查找要检查的每张卡

每当您必须检查序列中是否有任何特定项时,最好将序列转换为字典或哈希集。无论创建哪一个,都取决于您是只需要键还是具有键的元素。只创建一次字典/哈希集,并使用键快速搜索该项

在我们的例子中,我们只需要一个使用了guid的序列,在哪个事务中使用它并不重要

var usedGuids = transDetails.Select(transDetail => transDetail.TxnDetails).Distinct();
var hashedGuids = new HashSet(usedGuids);
我做了两次陈述,以便更容易理解所做的事情

现在,只要我有一个GUID,我就可以非常快速地检查它是否被使用:

bool guidIsUsed = usedGuids.Contains(myGuid);
因此,您在cardDetails中的卡序列(GUID不在transDetails中)为:

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

使用LINQ的方法链接语法:

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

找到满足此条件的所有卡: 在我的交易列表中是否有任何交易具有在该特定卡的TxnDetails中找不到的TxnDetails

我可以在这里看到问题:

如果任何交易有另一张TxnId,而不是一张卡,那么很有可能是很高的,请退还这张卡


因此,基本上,如果您的交易列表中至少有两个不同的交易ID,您应该从查询中获取所有卡片

此查询实际上应该返回所有卡片,您确定它不返回任何卡片吗?是的,它不返回任何内容。我只想返回cardDetails中的第三个条目,即不包含第二个列表中的任何TxnDetails为什么TxnDetails.ToString当TxnDetails是字符串时?此查询的性能索引是n的平方。应该避免在循环中出现这种循环。尝试连接两个列表,以便使用哈希表。@MatthewWatson实际上在我的代码中,TxnDetails是一个Guid,我将其转换为字符串。在这里,我只是将TxnDetails更改为string,但没有更新linq查询。在这种特殊情况下,您也可以使用EndsWith。是的,这是最简单的解决方案。实际上,问题是Any应该改为All。@SaedAmini实际上我正在从azure数据库获取所有数据,并且它仅以这种格式提供。我主要关心的是b将如何
e列表中10万个条目的性能是的,您是对的!:我改变了它,它工作了。谢谢
var hashedGuids = new HashSet(transDetails.Select(transDetail => transDetail.TxnDetails).Distinct());
var requestedCards = cardDetails.Where(card => !hashedGuids.Contains(card.Guid));
List<Card> result = cardDetails.Where(
    card => !transDetails.Exists(
         tran => tran.TxnDetails == card.TransactionRef.Split('|')[1]
)).ToList();
 cardDetails = cardDetails.Where(x => transDetails.Any(y => x.TransactionRef.Contains(y.TxnDetails) == false)).ToList();