web-dev-qa-db-ja.com

Linq to Sqlのnull許容型を比較す​​る

Nullable ParentIdフィールドを持つCategoryエンティティがあります。以下のメソッドが実行され、categoryIdがnullの場合、結果はnullのように見えますが、ParentId値がnullのカテゴリがあります。

ここの問題は何ですか、何が欠けていますか?

public IEnumerable<ICategory> GetSubCategories(long? categoryId)
{
    var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId)
        .ToList().Cast<ICategory>();

    return subCategories;
}

ところで、条件を(c.ParentId == null)に変更すると、結果は正常に見えます。

40
Ali Ersöz

最初に行うことは、TSQLが生成されたものを確認するためにログを記録することです。例えば:

ctx.Log = Console.Out;

LINQ-to-SQLは、nullを少し矛盾して処理するようです(リテラルと値に依存):

using(var ctx = new DataClasses2DataContext())
{
    ctx.Log = Console.Out;
    int? mgr = (int?)null; // redundant int? for comparison...
    // 23 rows:
    var bosses1 = ctx.Employees.Where(x => x.ReportsTo == (int?)null).ToList();
    // 0 rows:
    var bosses2 = ctx.Employees.Where(x => x.ReportsTo == mgr).ToList();
}

したがって、私が提案できるのは、ヌルを含むトップフォームを使用することだけです!

つまり.

Expression<Func<Category,bool>> predicate;
if(categoryId == null) {
    predicate = c=>c.ParentId == null;
} else {
    predicate = c=>c.ParentId == categoryId;
}
var subCategories = this.Repository.Categories
           .Where(predicate).ToList().Cast<ICategory>();

更新-カスタムExpressionを使用して「適切に」動作するようになりました。

    static void Main()
    {
        ShowEmps(29); // 4 rows
        ShowEmps(null); // 23 rows
    }
    static void ShowEmps(int? manager)
    {
        using (var ctx = new DataClasses2DataContext())
        {
            ctx.Log = Console.Out;
            var emps = ctx.Employees.Where(x => x.ReportsTo, manager).ToList();
            Console.WriteLine(emps.Count);
        }
    }
    static IQueryable<T> Where<T, TValue>(
        this IQueryable<T> source,
        Expression<Func<T, TValue?>> selector,
        TValue? value) where TValue : struct
    {
        var param = Expression.Parameter(typeof (T), "x");
        var member = Expression.Invoke(selector, param);
        var body = Expression.Equal(
                member, Expression.Constant(value, typeof (TValue?)));
        var lambda = Expression.Lambda<Func<T,bool>>(body, param);
        return source.Where(lambda);
    }
29
Marc Gravell

他の方法:

Where object.Equals(c.ParentId, categoryId)

または

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
53
ariel

演算子Equalsを使用する必要があります。

 var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId))
        .ToList().Cast<ICategory>();

Equalsfot nullable types戻り値true次の場合:

  • HasValueプロパティはfalseで、他のパラメーターはnullです。つまり、2つのnull値は定義上等しいです。
  • HasValueプロパティはtrueであり、Valueプロパティによって返される値は他のパラメーターと同じです。

次の場合、falseを返します。

  • 現在のNullable構造のHasValueプロパティはtrueであり、他のパラメーターはnullです。
  • 現在のNullable構造のHasValueプロパティはfalseであり、他のパラメーターはnullではありません。
  • 現在のNullable構造のHasValueプロパティはtrueであり、Valueプロパティによって返される値は他のパラメーターと等しくありません。

詳細はこちら Nullable <.T> .Equals Method

5
algreat

私の推測では、それはDBMSのかなり一般的な属性によるものである-2つのものが両方ともnullであるからといって、それらが等しいというわけではありません。

少し詳しく説明するには、次の2つのクエリを実行してください。

SELECT * FROM TABLE WHERE field = NULL

SELECT * FROM TABLE WHERE field IS NULL

"IS NULL"構造の理由は、DBMSの世界ではNULL!= NULLであるためです。NULLの意味は、値が未定義であることです。 NULLは未定義を意味するため、2つのNULL値が等しいと言うことはできません。定義により、それらが何であるかがわからないからです。

「field == NULL」を明示的にチェックすると、おそらくLINQはそれを「field IS NULL」に変換します。しかし、変数を使用すると、LINQは自動的に実行しないと推測しますその変換。

MSDNフォーラムの投稿 は、この問題に関する詳細情報です。

良い「チート」のように見えますが、ラムダを次のように変更することです。

c => c.ParentId.Equals(categoryId)
5
Eric Petroelje

または、単純にこれを使用できます。また、より良いSQLクエリに変換されます

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
1
Jiří Herník

このような単純なものはどうですか?

public IEnumerable<ICategory> GetSubCategories(long? categoryId)
{
    var subCategories = this.Repository.Categories.Where(c => (!categoryId.HasValue && c.ParentId == null) || c.ParentId == categoryId)
        .ToList().Cast<ICategory>();

    return subCategories;
}
1
Ryan Versaw

Linq to EntitiesはNull Coelescing(??)をサポートしているため、その場でnullをデフォルト値に変換するだけです。

Where(c => c.ParentId == categoryId ?? 0)
0
Kevbo