sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

LINQ 式のデータベースアクセスでクラス/インターフェースの型などを含めるとエラーになるときの対処

C#LINQ 式によるデータベースアクセスの際に、クラス/インターフェースの値など、プリミティブ型ではない型を含めるとエラーになることがあります。

具体的なエラー内容

たとえば、次のようなコードが考えられます。

問題例

public async Task<IEnumerable<IProduct>> SearchProductsAsync(string orderNo, string refMasterNo, string productName, IEndUser endUser, bool needHidden, int limit)
    {
        List<Product> products;

        using (var context = new DbContext())
        {
            products = await context.Products
                .Where(p => !string.IsNullOrEmpty(orderNo) ? p.OrderNo.Contains(orderNo) : true)
                .Where(p => !string.IsNullOrEmpty(refMasterNo) ? p.RefMasterNo.Contains(refMasterNo) : true)
                .Where(p => !string.IsNullOrEmpty(productName) ? p.Name.Contains(productName) : true)
                .Where(endUser != null ? p.EndUserId == endUser.Id : true) // エラー
                .Where(p => needHidden ? true : p.IsDeleted == false)
                .Take(limit)
                .ToListAsync();
        }

        return products;
    }

テーブルから、入力のあった値だけ絞り込みをかけたい場合、上述のように記述することもできると思いますが、これだとエラーになってしまいます。

Unable to create a constant value of type '***.Services.Db.Interfaces.IEndUser'.
Only primitive types or enumeration types are supported in this context

LINQ を生成するときに、enum やプリミティブ型以外のものを持ってくると、正しいコードが生成できなくてエラーになってしまいます。コードをシンプルにしてやる必要があるわけですね。

対応例

先にプリミティブ型 int にデータを移してから、LINQ を実行します。

public async Task<IEnumerable<IProduct>> SearchProductsAsync(string orderNo, string refMasterNo, string productName, IEndUser endUser, bool needHidden, int limit)
    {
        List<Product> products;
        int endUserId = endUser?.Id ?? 0;

        using (var context = new DbContext())
        {
            products = await context.Products
                .Where(p => !string.IsNullOrEmpty(orderNo) ? p.OrderNo.Contains(orderNo) : true)
                .Where(p => !string.IsNullOrEmpty(refMasterNo) ? p.RefMasterNo.Contains(refMasterNo) : true)
                .Where(p => !string.IsNullOrEmpty(productName) ? p.Name.Contains(productName) : true)
                .Where(p => endUserId > 0 ? p.EndUserId == endUserId : true)
                .Where(p => needHidden ? true : p.IsDeleted == false)
                .OrderBy(p => p.UpdatedDateTime)
                .Take(limit)
                .ToListAsync();
        }

        return products;
    }

考察

単純に DB 問い合わせをするクエリの中に、知らない型を含めたらダメじゃないか(そこまで都合よく最適化してくれない)という理解でいいと思います。

という記事があって、これも DB データとローカルのコレクションをおそらく Join しようとしてエラーになっているのだと思います。なので、DB アクセスをする処理のときは、ローカルなデータから利用する型は、プリミティブ型だけに制限できているのか注意が必要です。

エラー発生の雰囲気がちょっとだけ通常と違う不思議な感じ(コンパイル前の構文チェックでは問題ないし)なので、原因は何になるのかドキっとするかもしれないけど、エラーメッセージにちゃんと原因(理由)が書いてありました。

エラー内容を読んであげようという学び(常識だった)

参考