带可选参数的Linq/EF动态分组

本文关键字:EF 动态 Linq 参数 | 更新日期: 2023-09-27 18:25:37

我的搜索/组功能遇到问题。

上下文

我有一个客户对象列表(实体框架上下文),我想在这个列表中找到所有可能的重复项。如果一个对象是重复的,则条件应该是动态的。比方说可以通过UI进行选择。

型号

让我们假设以下部分是给定的。

我的客户类别

public class Customer
{
    public int CustomerId { get; set; }
    public string SearchName { get; set; }
    public string Mail { get; set; }
    public DateTime? Birthday { get; set; }
    public string CardNumber { get; set; }
    public DateTime Created { get; set; }
}

重复的可能条件是:SearchName、Mail、Birthday和CardNumber。

以下函数返回适当的结果:

    public IList<Customer> GetPossibleDuplicates()
    {
        IList<Customer> list;
        list =
            (from s in this.Context.Customers
                group s by new
                           {
                               s.SearchName, 
                               s.CardNumber
                           }
                into g where g.Count() > 1 select g)
                .SelectMany(g => g)
                .OrderBy(o => o.SearchName)
                .ThenBy(c => c.Created)
                .ToList();

        return list;
    }

我遇到的问题是通过语句使分组成为"动态的",这样就可以根据所选的creteria进行分组。有什么好的解决方案的建议吗?

带可选参数的Linq/EF动态分组

Enumerable.GroupBy允许IEqualityComparer的参数。请参阅MSDN页面。

此比较器可能是查询的动态部分。您声明分组是基于选择标准的,因此这将给您:

public IList<Customer> GetPossibleDuplicates()
{
    var comparer = SomeMethodReturningAnEqualityComparerBasedOnSelectionCriteria();
    return this.Context.Customers
            .GroupBy(customer => customer, comparer)
            .SelectMany(g => g)
            .OrderBy(o => o.SearchName)
            .ThenBy(c => c.Created)
            .ToList();
}

返回比较器的方法将返回IEqualityComparer<Customer>

如何依靠围绕IComparable 构建的标准

Func<Customer, IComparable> criteria

并且你的查询封装在一个方法中看起来像这个

public IList<Customer> FindByCriteria(Func<Customer, IComparable> criteria)
{
    var query = from customer in FindAll()
                group customer by criteria(customer)
                into groups
                where groups.Count() > 1
                from item in groups
                orderby item.SearchName, item.Created
                select item;
    return query.ToList();
}

然后你可以玩你自己的数据结构,甚至Tuple

void Main()
{
    var repository = new Repository();
    //Find all
    repository.FindAll().Dump();
    // Find by mail
    var mail = new Func<Customer, IComparable>(customer =>
    {
        return Tuple.Create(customer.Mail);
    });
    repository
        .FindByCriteria(mail)
        .Dump();
    // Find by mail and card number
    var multi = new Func<Customer, IComparable>(customer =>
    {
        return Tuple.Create(customer.CardNumber, customer.Mail);
    });
    repository
        .FindByCriteria(multi)
        .Dump();
}

完整的存储库代码位于下方

public class Repository
{
    public IList<Customer> FindByCriteria(Func<Customer, IComparable> criteria)
    {
        var query = from customer in FindAll()
                    group customer by criteria(customer)
                    into groupings
                    where groupings.Count() > 1
                    from grouping in groupings
                    orderby grouping.SearchName, grouping.Created
                    select grouping;
        return query.ToList();
    }
    public IEnumerable<Customer> FindAll()
    {
        yield return new Customer
        {
            CustomerId = 1,
            SearchName = "John",
            CardNumber = "0000 0000 0000 0000 1",
            Mail = "john.doe@test.com",
        };
        yield return new Customer
        {
            CustomerId = 2,
            SearchName = "Jim",
            CardNumber = "0000 0000 0000 0000 2",
            Mail = "jim.doe@test.com",
        };
        yield return new Customer
        {
            CustomerId = 3,
            SearchName = "Jack",
            CardNumber = "0000 0000 0000 0000 3",
            Mail = "jack.doe@test.com",
        };
        yield return new Customer
        {
            CustomerId = 4,
            SearchName = "Jane",
            CardNumber = "0000 0000 0000 0000 3",
            Mail = "john.doe@test.com",
        };
        yield return new Customer
        {
            CustomerId = 4,
            SearchName = "Joan",
            CardNumber = "0000 0000 0000 0000 3",
            Mail = "john.doe@test.com",
        };
    }
}

您的查询可以重写为:

var result = this.Context.Customers
    .GroupBy(x => new { x.SearchName, x.CardNumber })
    .Where(x => x.Count() > 1)
    .SelectMany(x => x)
    .OrderBy(x => x.SearchName)
    .ThenBy(x => x.Created)
    .ToList();

您可以看到.GroupBy(...)调用采用类型为Expression<Func<Customer, TKey>>的表达式,其中TKey是匿名类型。因此,如果您能够动态生成表达式,那么您就可以解决问题。

我必须承认,匿名类型是由编译器创建的,因此您的C#代码中还没有该类型。