Code Copied

Lambda的WhereIn和Dictionary分组应用

前置条件和约束

首先下面讲述的场景是一般的采购业务,采购申请单用PR代替,采购订单用PO代替。

采购申请单有其对应的采购申请明细,每项明细对应到一项物品,用PR Detail代替。

采购订单也有其对应的采购订单明细,每项明细对应到一项物品,用PO Detail代替。

本文中的数据层实现使用的是Entity Framework。

需求描述

在采购业务流程中,审批完成的PR需要生成PO,采购担当在生成PO的时候应该看到的是所有审批完成的PR Detail信息。

PO是以供应商为单位的,所以采购担将选中的PR Detail生成PO时,需要对明细数据进行分组。

image

根据这项需求,在程序上我们可以将其拆分为2个任务点:

1. 获取采购担从画面上选择的PR Detail数据

2. 根据选中的PR Detail按供应商进行分组

实体对象

实体对象的描述如下图所示:

image

开始任务

任务1. 获取采购担当从画面上选择的PR Detail数据

如果是使用sql语句来完成这个任务,我们首先想到的是使用in语句来获取PR Detail信息。

但是在这里我们不允许使用sql,只有放弃这种方式,那么使用Lambda是可行的选择。

Lambda却并没有直接提供类似于sql where in的语法,但我们可以对Lambda进行扩展。

QueryaleHelper.cs:

public static class QueryableHelper
{
    private static Expression<Func<TElement, bool>> GetWhereInExpression<TElement, TValue>(Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
    {
        ParameterExpression p = propertySelector.Parameters.Single();
        if (!values.Any())
            return e => false;

        var equals = values.Select(value => (Expression)Expression.Equal(propertySelector.Body, Expression.Constant(value, typeof(TValue))));
        var body = equals.Aggregate(Expression.Or);

        return Expression.Lambda<Func<TElement, bool>>(body, p);
    }

    /// <summary>  
    /// Return the element that the specified property's value is contained in the specifiec values  
    /// </summary>  
    /// <typeparam name="TElement">The type of the element.</typeparam>  
    /// <typeparam name="TValue">The type of the values.</typeparam>  
    /// <param name="source">The source.</param>  
    /// <param name="propertySelector">The property to be tested.</param>  
    /// <param name="values">The accepted values of the property.</param>  
    /// <returns>The accepted elements.</returns>  
    public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, params TValue[] values)
    {
        return source.Where(GetWhereInExpression(propertySelector, values));
    }

    /// <summary>  
    /// Return the element that the specified property's value is contained in the specifiec values  
    /// </summary>  
    /// <typeparam name="TElement">The type of the element.</typeparam>  
    /// <typeparam name="TValue">The type of the values.</typeparam>  
    /// <param name="source">The source.</param>  
    /// <param name="propertySelector">The property to be tested.</param>  
    /// <param name="values">The accepted values of the property.</param>  
    /// <returns>The accepted elements.</returns>  
    public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
    {
        return source.Where(GetWhereInExpression(propertySelector, values));
    }  
}

使用方法:

// 获取画面上选择的checkbox
var checkedIds = formCollection.AllKeys.Where(k => k.Contains("checkbox_")).ToList();
// 每个选中的checkbox都包含了Purchase Request Detail的Id值
IList<int> selectedIds = checkedIds.Select(c => int.Parse(formCollection[c])).ToList();
// 使用扩展的WhereIn方法获取选中的Purchase Request Detail对象列表
IList<PurchaseRequestDetail> purchaseRequestDetails =
    db.PurchaseRequestDetails.WhereIn(t => t.Id, selectedIds).ToList();

变量db是EntityFramework的DbContext对象。

任务2:根据选中的PR Detail按供应商分组

根据下图中的数据,最终这些数据按照供应商可分为3组,即可以生成3个PO:分别是供应商得力、晨光和联想的。

image

按照惯性的思维,我们会去遍历IList<PurchaseRequestDetail>然后将分组的值存放到一个IDictionary<string, List<PurchaseRequestDetail>>变量中。

循环写法:

IDictionary<string, List<PurchaseRequestDetail>> groupedPrDetailDictionary = new Dictionary<string, List<PurchaseRequestDetail>>();
foreach (var prd in purchaseRequestDetails)
{
    List<PurchaseRequestDetail> prdList;
    if (!groupedPrDetailDictionary.TryGetValue(prd.SupplierCode, out prdList))
    {
        prdList = new List<PurchaseRequestDetail>();
        groupedPrDetailDictionary.Add(prd.SupplierCode, prdList);
    }
    prdList.Add(prd);
}

Lambda写法:

IDictionary<string, List<PurchaseRequestDetail>> groupedPrDetailDictionary = new Dictionary<string, List<PurchaseRequestDetail>>();
groupedPrDetailDictionary = purchaseRequestDetails.GroupBy(g => g.SupplierCode)
    .ToDictionary(g => g.Key, g => g.ToList());

通过对比这2种实现方式,Lambda不仅减少了代码数量,而且在代码可读性上更优于循环写法。

总结

本篇按照采购业务中的一个实际例子,描述了如何灵活地使用Lambda,Lambda提供了良好的可扩展性,在某些情况下能够简化很多编码工作。