11

次の Linq-to-Entities クエリは正常に機能します。

var query = repository.Where(r => r.YearProp1.HasValue &&
                                  r.YearProp1 >= minYear &&
                                  r.YearProp1 <= maxYear);

short?私のデータベースには、すべてが年関連の情報 (データ型)を報告する 12 ほどの列があります。これらすべての列に同じ Linq-to-Entities ロジックを再利用したいと考えています。何かのようなもの:

Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
                                  fx(r) >= minYear &&
                                  fx(r) <= maxYear);

これにより、次のエラーが発生します。

LINQ to Entities はメソッド 'System.Nullable`1[System.Int16] fx(RepoEntity)' メソッドを認識せず、このメソッドはストア式に変換できません。

エラーが発生する理由は理解していますが、SQL クエリが動作しているプロパティを変更するためだけにコードを何十回も複製する必要のない回避策があるかどうか疑問に思っています。

関数を複数のクエリで再利用するので、私の質問の一般的なバージョンは次のとおりだと思います。単純なプロパティ ゲッター ラムダ関数を、Linq-to-Entities で消費できる式に変換する方法はありますか?

4

3 に答える 3

2

Raphaël Althaus の回答から構築しますが、最初に探していた汎用セレクターを追加します。

public static class Examples
{
    public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
    {
        return x => x.PropertyOne;
    }

    public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
    {
        return x => x.PropertyTwo;
    }

    public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
    {
        var param = Expression.Parameter(typeof(TEntity), "entity");
        var member = Expression.Invoke(selector, param);

        Expression hasValue = Expression.Property(member, "HasValue");
        Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
                                             Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
        Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
                                          Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));

        Expression body = Expression.AndAlso(hasValue,
                      Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));

        return Expression.Lambda<Func<TEntity, bool>>(body, param);
    }
}

探していた元のクエリのように使用できます。

Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;

var query = Context
            .MyEntities
            .Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
于 2012-06-18T21:16:39.370 に答える
1

述語は、それ自体が bool と評価される必要があるフィルターです (結果に含めるかどうかについて)。メソッドを次のように作り直すと、うまくいくはずです。

public static Expression<Func<RepoEntity, bool>> FitsWithinRange(int minYear, int maxYear)
{
    return w => w.HasValue && w >= minYear && w <= maxYear;
}

編集:ああ、それを使用するには:

var query = repository.Where(Repository.FitsWithinRange(minYear, maxYear));
于 2012-06-18T17:34:59.500 に答える
1

そのようなことを行うことができます(linq2エンティティで「そのまま」機能するかどうかはわかりませんが、問題がある場合は...教えてください)

利用方法

var query = <your IQueryable<T> entity>.NullableShortBetween(1, 3).ToList();

関数

public static IQueryable<T> NullableShortBetween<T>(this  IQueryable<T> queryable, short? minValue, short? maxValue) where T: class
        {
            //item (= left part of the lambda)
            var parameterExpression = Expression.Parameter(typeof (T), "item");

            //retrieve all nullable short properties of your entity, to change if you have other criterias to get these "year" properties
            var shortProperties = typeof (T).GetProperties().Where(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));

            foreach (var shortProperty in shortProperties)
            {
                //item (right part of the lambda)
                Expression memberExpression = parameterExpression;
                //item.<PropertyName>
                memberExpression = Expression.Property(memberExpression, shortProperty);
                //item.<PropertyName>.HasValue
                Expression firstPart = Expression.Property(memberExpression, "HasValue");
                //item.<PropertyName> >= minValue
                Expression secondPart = Expression.GreaterThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(minValue), typeof (short?)));
                //item.<PropertyName> <= maxValue
                var thirdPart = Expression.LessThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(maxValue), typeof (short?)));
                //item.<PropertyName>.HasValue && item.<PropertyName> >= minValue
                var result = Expression.And(firstPart, secondPart);
                //item.<PropertyName>.HasValue && item.<PropertyName> >= minValue && item.<PropertyName> <= maxValue
                result = Expression.AndAlso(result, thirdPart);
                //pass the predicate to the queryable
                queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(result, new[] {parameterExpression}));
            }
            return queryable;
        }

編集:「単純な」リフレクションに基づく別のソリューションで、必要なものとして「見える」

public static short? GetYearValue<T>(this T instance)
        {
            var propertyInfo = typeof(T).GetProperties().FirstOrDefault(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
            return propertyInfo.GetValue(instance, null) as short?;
        }

利用方法

var result = list.Where(item => item.GetYearValue() != null && item.GetYearValue() >= 1 && item.GetYearValue() <= 3).ToList();
于 2012-06-18T20:28:24.740 に答える