Expression應用:把它當參數用

在寫商業層(Business Logic Layer)或資料存取層(Data Access Layer),最覺得麻煩的是,明明是同樣的資料要出輸,要為個個不同的需求做條件篩選,每一個條件都要寫成一個參數,然後一堆的if判斷,而以多一個條件又要多一個參數,或是寫一個新的Method,不小心還會影響其他地方,我覺得是一個焦油坑,就在思考有什麼方式比較好,就讓我想到Linq的方式,用Expression。

在寫商業層(Business Logic Layer)或資料存取層(Data Access Layer),最覺得麻煩的是,明明是同樣的資料要出輸,要為個個不同的需求做條件篩選,每一個條件都要寫成一個參數,然後一堆的if判斷,而以多一個條件又要多一個參數,或是寫一個新的Method,不小心還會影響其他地方,我覺得是一個焦油坑,就在思考有什麼方式比較好,就讓我想到Linq的方式,用Expression。

 

 

以前的寫法


GetProducts1("Code", "Wade", "C#", "Name", "ASC", 0, 20);

//如果有新的欄位又要多一個參數,麻煩!!
public IEnumerable<Product> GetProducts1(string name, string owner, string catalog, string orderby = "Name", string orderOriented = "ASC", int skip = 0, int take = 20)
{
    var query = _proRepository.AsQueryable();
    if (string.IsNullOrWhiteSpace(name))
    {
        query = query.Where(q => q.Name.Contains(name));
    }

    //......忽略部分Code。

    if (orderOriented == "ASC")
    {
        query = query.OrderBy(orderby); //有寫以字串OrderBy的Extension
    }
    else
    {
        query = query.OrderByDescending(orderby);
    }

    query = query.Skip(0);
    query = query.Take(take);

    return query.ToArray();
}

 

前些日子的嘗試


GetProducts2(q => q.Name.Contains("Code") || q.EndDate > DateTime.Today);

//後來直接把Where抽出去,由呼叫端自己去決定要篩選什麼。
public IEnumerable<Product> GetProducts2(Expression<Func<Product, bool>> where, string orderby = "Name", string orderOriented = "ASC", int skip = 0, int take = 20)
{
    var query = _proRepository.AsQueryable();

    if (where != null)
    {
        //由呼叫端自己去決定要篩選什麼。
        query = _proRepository.Where(where);
    }

    //......忽略部分Code。

    if (orderOriented == "ASC")
    {
        query = query.OrderBy(orderby); //有寫以字串OrderBy的Extension
    }
    else
    {
        query = query.OrderByDescending(orderby);
    }

    query = query.Skip(0);
    query = query.Take(take);

    return query.ToArray();
}

 

最近的嘗試


GetProducts3(q => q.OrderBy(x => x.ListPrice), q => q.Where(x => x.Status == "Active"), q => q.Take(500));

//更後來還嘗試,所有條件由呼叫端決定。
public IEnumerable<Product> GetProducts3(params Expression<Func<IQueryable<Product>, IQueryable<Product>>>[] exprs)
{
    bool orderby = false, take = false;
    int takeAmount = 20;
    var query = _proRepository.AsQueryable();

    if (exprs != null)
    {
        foreach (var expr in exprs)
        {
            //做一個簡單的檢查有沒有呼叫orderby, take
            if (expr.Body is MethodCallExpression)
            {
                var mce = expr.Body as MethodCallExpression;
                switch (mce.Method.Name)
                {
                    case "OrderBy":
                        orderby = true;
                        break;
                    case "OrderByDescending":
                        orderby = true;
                        break;
                    case "Take":
                        var takeArg = mce.Arguments[1] as ConstantExpression;
                        if ((int)takeArg.Value > 1000)
                        {
                            //最大只能1000
                            takeAmount = 1000;
                            continue;
                        }

                        take = true;
                        break;
                    default:
                        break;
                }
            }

            //將Expression轉成Delegate
            query = expr.Compile().Invoke(query);
        }
    }

    //一定要排序
    if (!orderby)
    {
        query = query.OrderBy(x => x.Name);
    }

    //一定要限制取的量
    if (!take)
    {
        query = query.Take(takeAmount);
    }

    return query.ToArray();
}

 

我個人覺得,像Select部分保留較大彈性,讓呼叫端可以客製他要的東西,多一個條件要篩選,呼叫端自己決定就可以了,不用改BLL或DAL,事實上我不知道這樣寫是好還是壞,不過挺方便的就是了。