2013-03-01 16 views
6

Ich bin beschäftigt Erstellen von Wrapper-Erweiterung Methoden auf Dapper und DapperExtensions. Im Moment versuche ich, der Erweiterungsmethode GetList<T> eine Filterung hinzuzufügen, ähnlich der LINQ-Erweiterungsmethode Where<T>. Ich habe this question gesehen, aber es scheint, ich kann nicht implementieren, was Marc Gravell suggested, weil es keinen Typ EqualsExpression in .NET 4.5 gibt. Hier finden Sie einige Demo-Code mit der Erklärung meines Problems zu helfen:Auseinanderziehen Ausdruck <Func <T, object>>

using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Data; 
using System.Data.SqlClient; 
using System.Diagnostics; 
using System.Linq.Expressions; 
using DapperExtensions; 

namespace Dapper.Extensions.Demo 
{ 
    public class Program 
    { 
     private static readonly string ConnectionString = ConfigurationManager.ConnectionStrings["DapperDbContext"].ConnectionString; 
     public static IDbConnection Connection { get { return new SqlConnection(ConnectionString); } } 

     public static void Main(string[] args) 
     { 
      const int marketId = 2; 
      var matchingPeople = Connection.Get<Person>(p => p.MarketId, marketId); // This works 

      // Below is a LambdaExpression. expression.Body is, bizarrely, a UnaryExpression with a Convert 
      //var matchingPeople = Connection.Get<Person>(p => p.MarketId == marketId); // Does not work 

      foreach (var person in matchingPeople) 
      { 
       Console.WriteLine(person); 
      } 

      if (Debugger.IsAttached) 
       Console.ReadLine(); 
     } 
    } 

    public static class SqlConnectionExtensions 
    { 
     public static IEnumerable<T> Get<T>(this IDbConnection connection, Expression<Func<T, object>> expression, object value = null) where T : class 
     { 
      using (connection) 
      { 
       connection.Open(); 

       // I want to be able to pass in: t => t.Id == id then: 
       // Expression<Func<T, object>> expressionOnLeftOfFilterClause = t => t.Id; 
       // string operator = "=="; 
       // object valueFromLambda = id; 
       // and call Predicates.Field(expressionOnLeftOfFilterClause, Operator.Eq, valueFromLambda) 

       var predicate = Predicates.Field(expression, Operator.Eq, value); 
       var entities = connection.GetList<T>(predicate, commandTimeout: 30); 
       connection.Close(); 
       return entities; 
      } 
     } 
    } 

    public class Person 
    { 
     public int Id { get; set; } 

     public string FirstName { get; set; } 

     public string Surname { get; set; } 

     public int MarketId { get; set; } 

     public override string ToString() 
     { 
      return string.Format("{0}: {1}, {2} - MarketId: {3}", Id, Surname, FirstName, MarketId); 
     } 
    } 
} 

besondere Berücksichtigung meiner Get<T> Extension-Methode: Wenn ich entweder passieren in p => p.MarketId oder p => p.MarketId == marketId, expression.Body ist vom Typ UnaryExpression. Für Letzteres enthält expression.Body tatsächlich {Convert((p.MarketId == 2))}.

Versuch

var binaryExpression = expression as BinaryExpression; 

kehrt null, was schade ist, denn es gibt Left und Right Eigenschaften, die ich nützlich gefunden haben könnte.

Also, weiß jemand, wie man erreicht, was ich will? Weiter unten möchte ich in der Lage sein, die Operator enum basierend auf dem Lambda-Ausdruck auszuwählen, die übergeben wurde. Jede Hilfe würde sehr geschätzt werden.

+0

Meine Vermutung wäre, dass die 'Operand' Eigenschaft der' UnaryExpression' die 'BinaryExpression' ist, die Sie suchen. – Iridium

+0

@Iridium Ich denke du hast recht; durch Debugging kann ich sehen, dass der 'Operand' von' expression.Body' vom Typ 'LogicalBinaryExpression' ist, aber wie kann ich auf 'expression.Body.Operand' in-code zugreifen? Intellisense kann das nicht lösen? –

+0

@Iridium ist absolut richtig, gerade versucht es jetzt: 'BinaryExpression binary = (expr.Body als UnaryExpression) .Operand als BinaryExpression;' ergibt die viel gewünschte 'BinaryExpression'. @ Jon Skeet erklärte Ihnen genau, warum die Konvertierung erscheint (Werttypen werden in "Objekt" eingerahmt) ... Vorsicht vor noch komplexeren Ausdrucksbäumen, die Sie selbst vielleicht selbst senden. Sie können nicht einen Ausdruck Baum Parser, der ein hohes Spektrum an Möglichkeiten auf der Seite des Benutzers Programmierer hat und sehr große Annahmen auf der Bibliothek Writer Seite ... –

Antwort

5

Ich habe herausgefunden, wie ich erreichen kann, was ich will.

Zusammengefasst:

  1. Ich brauche eine Erweiterungsmethode, die GetList<T> Erweiterungsmethode DapperExtension des einwickelt.
  2. Letzteres kann ein Prädikat vom Typ IFieldPredicate aufnehmen, mit dem ich der auszuführenden SQL-Abfrage einen Filter hinzufügen kann. Ich kann dies erreichen, indem ich Predicates.Field<T>(Expression<Func<T, object>> expression, Operator op, object value) verwende.
  3. Das Problem liegt in der Umwandlung eines einfachen Lambda-Ausdrucks t => t.Id == id in Parameter für Predicates.Field<T>.Also, konzeptionell muss ich den Lambda-Ausdruck in drei Teile zerlegen: t => t.Id, Operator.Eq und id.

Mit Hilfe von @Iridium, @Eduard und @ Jon, meine endgültige Lösung ist:

public static class SqlConnectionExtensions 
{ 
    public static IEnumerable<T> Get<T>(this IDbConnection connection, Expression<Func<T, object>> expression) where T : class 
    { 
     using (connection) 
     { 
      connection.Open(); 

      var binaryExpression = (BinaryExpression)((UnaryExpression) expression.Body).Operand; 

      var left = Expression.Lambda<Func<T, object>>(Expression.Convert(binaryExpression.Left, typeof(object)), expression.Parameters[0]); 
      var right = binaryExpression.Right.GetType().GetProperty("Value").GetValue(binaryExpression.Right); 
      var theOperator = DetermineOperator(binaryExpression); 

      var predicate = Predicates.Field(left, theOperator, right); 
      var entities = connection.GetList<T>(predicate, commandTimeout: 30); 

      connection.Close(); 
      return entities; 
     } 
    } 

    private static Operator DetermineOperator(Expression binaryExpression) 
    { 
     switch (binaryExpression.NodeType) 
     { 
      case ExpressionType.Equal: 
       return Operator.Eq; 
      case ExpressionType.GreaterThan: 
       return Operator.Gt; 
      case ExpressionType.GreaterThanOrEqual: 
       return Operator.Ge; 
      case ExpressionType.LessThan: 
       return Operator.Lt; 
      case ExpressionType.LessThanOrEqual: 
       return Operator.Le; 
      default: 
       return Operator.Eq; 
     } 
    } 
} 

ich jetzt tun können:

var matchingPeople = Connection.Get<Person>(p => p.MarketId == marketId); 

Ich weiß, wie brüchig das ist - es wird zerbrechen, wenn ich etwas Komplizierteres übergebe, oder sogar etwas, das gleich aussieht, wie var matchingPeople = Connection.Get<Person>(p => p.MarketId.Equals(marketId));. Es löst jedoch 90% meiner Fälle, daher bin ich damit zufrieden, es so zu belassen, wie es ist.

+1

Danke für diese Lösung. MYSQL: Es gab 1 Fehler, den ich reparierte, Wert sollte mit Mitglied ersetzt werden: var right = binaryExpression.Right.GetType(). GetProperty ("Mitglied"). GetValue (binaryExpression.Right); –

3

Das ist das Problem:

Expression<Func<T, object>> expression 

Ihre Funktion hat object zurückzukehren. Der Typ p.MarketId == marketId ist bool. Es muss daher in object eingerahmt werden, daher die Convert.

Wenn der Ausdruck immer versteht man ein Prädikat sein, sollten Sie es ändern:

Expression<Func<T, bool>> expression 

An diesem Punkt würde ich erwarten, dass Sie den entsprechenden binären Ausdruck zu sehen. Auf der anderen Seite funktioniert das dann nicht für p => p.MarketId ...

Um ehrlich zu sein, ist es nicht wirklich klar, was die Parameter bedeuten sollen. Es fühlt sich an, als ob Sie vielleicht zwei Methoden wünschen - eine für einen einzigen Parameter, der ein Prädikat ist, und einen für zwei Parameter: eine Projektion und einen Zielwert.

+0

Jon, ja das stimmt; Ich muss jedoch "Expression >" in Predicates.Field übergeben. Gibt es eine elegante Möglichkeit, zwischen den beiden zu konvertieren? –

+0

@SameerSingh: Nun, es ist nicht klar, wo 'Predicates.Field' kommt sogar aus ... –

+0

Das ist von der DapperExtensions-Bibliothek, [Predicates.cs] (https://github.com/tmsmith/Dapper-Extensions/blob/master /DapperExtensions/Predicates.cs) –

Verwandte Themen