4

MappedItemクラスのリストを生成しようとすると、エラーが発生します。以下を参照してください。簡単に言うと、以下のコード例では、カテゴリ、日付範囲、SKUで商品を検索しようとしています。私が持っている要件は、ユーザーがSKUのコンマ区切りリストを入力できる必要があり、検索は、ユーザーが入力したSKUの1つでSKUが始まる製品を見つけることです。コードを実行すると、次のようになります。

ローカルシーケンスは、Contains()演算子を除くクエリ演算子のLINQtoSQL実装では使用できません。

省略されたシーケンスは次のとおりです。

SKUのコンマ区切り文字列を文字列のリストに変換します。

string sku = TextSKU.Text;
List<string> skuList = sku.Split(new char[] { ',' }).ToList();

コードの他の場所で、検索結果を受け入れるクラスを定義します。

public class MappedItem
{
    public string ItemDescription { get; set; }
    public int ItemCount { get; set; }

    public MappedItem()
    {

    }

    public MappedItem(string itemDescription, int itemCount)
    {
        ItemDescription = itemDescription;
        ItemCount = itemCount;
    }
}

これが私の結果を生成するクエリです

List<MappedItem> widgetItems = (from c1 in db.CCRCodes
                                join pac in db.widgetAssignedCodes on c1.code_id equals pac.code_id
                                join ph in db.widgetHistories on pac.history_id equals ph.history_id
                                where ph.contact_dt.Value.Date >= startDate && ph.contact_dt.Value.Date <= endDate &&
                                    (string.IsNullOrEmpty(baanCatFam) || ph.baan_cat_family_code == baanCatFam) &&
                                    (string.IsNullOrEmpty(baanCat) || ph.baan_cat_code == baanCat) &&
                                    (string.IsNullOrEmpty(baanSubCat) || (ph.baan_sub_cat_code == baanSubCat)) &&
                                    (string.IsNullOrEmpty(sku) || skuList.All(sl => ph.product_mod.StartsWith(sl)))
                                group c1 by c1.code_desc into ct
                                select new MappedItem
                                {
                                    ItemDescription = ct.Key.ToUpper(),
                                    ItemCount = ct.Count()
                                }).OrderByDescending(m => m.ItemCount)
                                .ToList();

犯人は、私が抽出して以下に表示したコード行であると思います。

skuList.All(sl => ph.product_mod.StartsWith(sl))

これは、ユーザーが入力したskusのコンマ区切りリストから派生したskuListの要素で始まるすべてのskusを識別します。私の質問は、このエラーの原因と、コード例を前提として、それらを回避するために何をするかです。

4

1 に答える 1

6

まず、論理的には、すべてではなく、いずれかが必要です。

第二に、これはクエリフィルターを構築するための悪い方法です。これらの操作はすべてデータベースに送信されますが、適用するフィルターを決定するための情報はすでにローカルになっています。明示的な結合も不適切です(代わりに関連付けプロパティを使用できます)。

IQueryable<WidgetHistory> query =  db.widgetHistories
  .Where(ph => ph.contact_dt.Value.Date >= startDate
    && ph.contact_dt.Value.Date <= endDate);

if (!string.IsNullOrEmpty(baanCatFam))
{
  query = query.Where(ph => ph.baan_cat_family_code == baanCatFam);
}
if (!string.IsNullOrEmpty(baanCat))
{
  query = query.Where(ph => ph.baan_cat_code == baanCat);
}
if (!string.IsNullOrEmpty(baanSubCat))
{
  query = query.Where(ph => ph.baan_sub_cat_code == baanSubCat);
}

//TODO sku filtering here.

List<MappedItem> widgetItems =
  from ph in query
  let c1 = ph.widgetAssignedCode.CCRCode
  group c1 by c1.code_desc into g
  select new MappedItem
  {
    ItemDescription = g.Key.ToUpper(),
    ItemCount = g.Count()
  }).OrderByDescending(m => m.ItemCount)
  .ToList();

第三に:あなたの質問への答え。

このエラーの原因

LinqToSqlのクエリプロバイダーは、ローカルコレクションをSQLに変換できません。変換できるシナリオのセットは限られています... .Where(ph => idList.Contains(ph.Id)) idListのintごとに1つのパラメーターを持つIN句に変換されます。

この制限を回避するには、ローカルコレクションを式に変換する必要があります。コレクション内の各アイテムをフィルタリング式に変換することから始めます。

List<Expression<Func<WidgetHistory, bool>>> skuFilters =
  skuList.Select<string, Expression<Func<WidgetHistory, bool>>>(skuItem =>
    ph => ph.ProductMod.StartsWith(skuItem)
  ).ToList();

次に、ヘルパーメソッド:

public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
  this IEnumerable<Expression<Func<T, bool>>> filters)
{
  Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault();
  if (firstFilter == null)
  {
    Expression<Func<T, bool>> alwaysTrue = x => true;
    return alwaysTrue;
  }
  var body = firstFilter.Body;
  var param = firstFilter.Parameters.ToArray();
  foreach (var nextFilter in filters.Skip(1))
  {
    var nextBody = Expression.Invoke(nextFilter, param);
    body = Expression.OrElse(body, nextBody);
  }
  Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
  return result;
}

そして今、それをすべてまとめます:

if (skuFilters.Any())  //this part goes into where it says "TODO"
{
  Expression<Func<WidgetHistory, bool>> theSkuFilter = skuFilters.OrTheseFiltersTogether()
  query = query.Where(theSkuFilter);
}
于 2011-02-16T18:19:51.130 に答える