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

时间:2015-08-24 08:15:13

标签: c# linq

我有两个对象,即交易

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

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

注意: TransactionRef的格式为Date|TxnID

我还有两个对象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'}

我想使用基于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条记录。所以表现也很重要。

5 个答案:

答案 0 :(得分:5)

这应该这样做

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

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

优化这一点的一种方法是将所有可能的事务详细信息存储在哈希集中。然后查找应该非常接近O(1)(假设公平的哈希码分布)而不是O(n) - 使算法的整体复杂度从O(n * k)下降到O(n + k)。 / p>

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;

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

答案 1 :(得分:2)

此查询应该可以解决问题:

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

但是,为什么要在一个字段中组合两个数据?我建议将TransactionRef类中的Card字段分为两个字段:TransactionDateTransactionID,以避免在查询中使用字符串操作。

答案 2 :(得分:1)

这个怎么样?

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

答案 3 :(得分:1)

它只是一个括号问题,== 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();

或建议的任何改进,但你的代码基本上非常接近正确;)

答案 4 :(得分:0)

如果表现很重要,我建议您首先给卡类一个属性,该属性返回'|'之后的部分字符。根据您想要执行此查询的频率与构建卡的频率相比,甚至可能让构造函数将transactionRef分隔为“|”之前的部分以及“|”之后的一部分。

您选择的方法对查询并不重要。我们假设类卡有一个属性:

string Guid {get {return ...;}

据我所知,你需要一系列来自序列cardDetails的所有卡片,这些卡片没有Guid,它们与transDetails序列中的任何TxnDetails相同。

或者换句话说:如果你要在TxnDetails中制作所有使用的guid的序列,你想要CardDetails中所有卡片的guid都不是所有使用的guid序列。

你可以使用Any(),但这意味着你必须为你要检查的每张卡搜索transDetails序列。

每当您必须检查特定项目是否在序列中时,最好将序列一次转换为Dictionary或HashSet。无论您创建哪个,取决于您是否只需要密钥或具有密钥的元素。仅创建一次字典/ hashset,并使用密钥快速搜索该项目。

在我们的例子中,我们只想要一个带有guid的序列,它与使用哪个Transaction无关。

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