23

プリミティブ値が与えられた場合、age次のような式を作成する方法を知っています。

//assuming: age is an int or some other primitive type
employee => employee.Age == age

これを行うことにより:

var entityType = typeof(Employee);
var propertyName = "Age";
int age = 30;
var parameter = Expression.Parameter(entityType, "entity");

var lambda = Expression.Lambda(
        Expression.Equal(
            Expression.Property(parameter, propertyName),
            Expression.Constant(age)
        )                    
    , parameter);

問題のプロパティと定数がプリミティブ型ではないシナリオを除いて、これは正常に機能します。

比較がオブジェクト間である場合、同様の式をどのように構築しますか?

EF を使用すると、次のように書くことができます。

Location location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location == location);

それも機能しますが、同じ式を作成しようとすると:

var entityType = typeof(Employee);
var propertyName = "Location";
var location = GetCurrentLocation();
var parameter = Expression.Parameter(entityType, "entity");

var lambda = Expression.Lambda(
        Expression.Equal(
            Expression.Property(parameter, propertyName),
            Expression.Constant(location)
        )                    
    , parameter);

次のようなエラーが表示されます。

Unable to create a constant value of type 'Location'. Only primitive types or enumeration types are supported in this context.

私の疑いは、Expression.Constant()プリミティブ型のみを期待しているため、別の式ファクトリ メソッドを使用する必要があるということです。(もしかしてExpression.Object? - 私はそれが存在しないことを知っています)

オブジェクトを比較する式を作成する方法はありますか? コンパイルされた LINQ ステートメントの場合は EF が正しく解釈できるのに、式の場合は解釈できないのはなぜですか?

4

3 に答える 3

7

以前の回答で言及されたことに加えて。より具体的な解決策は次のようになります。

public static Expression CreateExpression<T>(string propertyName, object valueToCompare)
{
    // get the type of entity
    var entityType = typeof(T);
    // get the type of the value object
    var valueType = valueToCompare.GetType();
    var entityProperty = entityType.GetProperty(propertyName);
    var propertyType = entityProperty.PropertyType;


    // Expression: "entity"
    var parameter = Expression.Parameter(entityType, "entity");

    // check if the property type is a value type
    // only value types work 
    if (propertyType.IsValueType || propertyType.Equals(typeof(string)))
    {
        // Expression: entity.Property == value
        return Expression.Equal(
            Expression.Property(parameter, entityProperty),
            Expression.Constant(valueToCompare)
        );
    }
    // if not, then use the key
    else
    {
        // get the key property
        var keyProperty = propertyType.GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0);

        // Expression: entity.Property.Key == value.Key
        return Expression.Equal(
            Expression.Property(
                Expression.Property(parameter, entityProperty),
                keyProperty
            ),
            Expression.Constant(
                keyProperty.GetValue(valueToCompare),
                keyProperty.PropertyType
            )
        );
    }
}

重要なポイント:

  1. 必ずヌルをチェックしてください
  2. propertyTypeおよび互換性があることを確認してくださいvalueType(同じタイプであるか、変換可能であるかのいずれか)。
  3. ここでは、いくつかの仮定が行われています (たとえば、 を割り当てるKeyAttributeなど) 。
  4. このコードはテストされていないため、正確にコピー/貼り付けできる状態ではありません。

それが役立つことを願っています。

于 2013-04-15T22:53:20.380 に答える
3

LocationEF は等値比較をSQL 式に変換する方法を知らないため、これを行うことはできません。

ただし、Location比較するプロパティがわかっている場合は、匿名型でこれを行うことができます。

var location = GetCurrentLocation();
var locationObj = new { location.LocationName, location.LocationDescription };
employees = DataContext.Employees.Where(e => new { e.Location.LocationName, e.Location.Description } == locationObj);

もちろん、それは次と同等です:

var location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location.LocationName == location.Name && 
                                             e.Location.Description == location.Description);
于 2013-04-12T18:01:08.733 に答える
2

以下のコードを実行してください。e => e.Location == location が Expression.Equal、Expression.Property、および Expression.Constant で構築できるものにコンパイルされているというあなたの仮定をテストしたかったのです。

    class Program {
       static void Main(string[] args) {
          var location = new Location();
          Expression<Func<Employee, bool>> expression = e => e.Location == location;

          var untypedBody = expression.Body;

          //The untyped body is a BinaryExpression
           Debug.Assert(
              typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()), 
              "Not Expression.Equal");

           var body = (BinaryExpression)untypedBody;
           var untypedLeft = body.Left;
           var untypedRight = body.Right;

           //The untyped left expression is a MemberExpression
           Debug.Assert(
              typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()), 
              "Not Expression.Property");

           ////The untyped right expression is a ConstantExpression
          //Debug.Assert(
          //   typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),                 
          //   "Not Expression.Constant");

          //The untyped right expression is a MemberExpression?
          Debug.Assert(
               typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType())));
    }
}

public class Employee
{
    public Location Location { get; set; }
}

public class Location { }

そうではないようです。これは、正しい式が定数ではないためです。これを確認するには、コメントアウトされたコードのコメントを外します。

私が理解していないのは、正しい式が MemberExpression である理由です。おそらく、linq 式コンパイラを知っている人なら、私よりも多くのことを理解できるでしょう。

編集:これは、ラムダのクロージャーに関係している可能性があります-クローズドオーバー変数を含むクラスが舞台裏で作成されます。場所は、そのクラスのメンバーである可能性があります。これについてはよくわかりませんが、それは私が疑っていることです。

この投稿は、状況をさらに明らかにする可能性があります。

于 2013-04-12T19:21:33.800 に答える