2012-05-13 5 views
5

inspiriert von this answer Ich versuche, eine Eigenschaft einer Modellklasse einem Ausdruck zuzuordnen, der auf der tatsächlichen Entität basiert. Dies sind die beiden beteiligten Klassen:Übersetzen der Ausdrucksbaumstruktur von einem Typ in einen anderen Typ mit komplexen Zuordnungen

public class Customer 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Id { get; set; } 
    public DateTime? BirthDate { get; set; } 
    public int CustomerTypeId { get; set; } 
} 

public class CustomerModel 
{ 
    ... 
    public bool HasEvenId { get; set; } 
} 

Ein Beispiel für einen möglichen Ausdruck Ich mag würde konvertieren ist:

Expression<Func<CustomerModel, bool>> from = model => model.HasEvenId; 
Expression<Func<Customer, bool>> to = entity => ((entity.Id % 2) == 0); 

Das Problem ist, dass ich einen OData-Endpunkt über ASP belichten. NET WebAPI, aber ich muss einige Operationen an den Entitäten vornehmen, bevor ich sie ausführen kann, daher die Notwendigkeit einer Modellklasse und die Notwendigkeit, den Ausdruck basierend auf dem Modell zu übersetzen, das ich als OData-Abfrage in einem Ausdruck basierend auf der Entität erhalten könnte , die ich verwenden würde, um EF4 abzufragen.

Dies ist, wo ich so weit gekommen:

private static readonly Dictionary<Expression, Expression> Mappings = GetMappings(); 

private static Dictionary<Expression, Expression> GetMappings() 
{ 
    var mappings = new Dictionary<Expression, Expression>(); 

    var mapping = GetMappingFor((CustomerModel model) => model.HasEvenId, (Customer customer) => (customer.Id%2) == 0); 
    mappings.Add(mapping.Item1, mapping.Item2); 

    return mappings; 
} 

private static Tuple<Expression, Expression> GetMappingFor<TFrom, TTo, TValue>(Expression<Func<TFrom, TValue>> fromExpression, Expression<Func<TTo, TValue>> toExpression) 
{ 
    MemberExpression fromMemberExpression = (MemberExpression) fromExpression.Body; 
    return Tuple.Create<Expression, Expression>(fromMemberExpression, toExpression); 
} 

public static Expression<Func<TTo, bool>> Translate<TFrom, TTo>(Expression<Func<TFrom, bool>> expression, Dictionary<Expression, Expression> mappings = null) 
{ 
    if (expression == null) 
     return null; 

    string parameterName = expression.Parameters[0].Name; 

    parameterName = string.IsNullOrWhiteSpace(parameterName) ? "p" : parameterName; 

    var param = Expression.Parameter(typeof(TTo), parameterName); 
    var subst = new Dictionary<Expression, Expression> { { expression.Parameters[0], param } }; 
    ParameterChangeVisitor parameterChange = new ParameterChangeVisitor(parameterName); 
    if (mappings != null) 
     foreach (var mapp in mappings) 
      subst.Add(mapp.Key, parameterChange.Visit(mapp.Value)); 

    var visitor = new TypeChangeVisitor(typeof(TFrom), typeof(TTo), subst); 
    return Expression.Lambda<Func<TTo, bool>>(visitor.Visit(expression.Body), param); 
} 

public IQueryable<CustomerModel> Get() 
{ 
    var filterExtractor = new ODataFilterExtractor<CustomerModel>(); 
    Expression<Func<CustomerModel, bool>> expression = filterExtractor.Extract(Request); 
    Expression<Func<Customer, bool>> translatedExpression = Translate<CustomerModel, Customer>(expression, Mappings); 

    IQueryable<Customer> query = _context.Customers; 

    if (translatedExpression != null) 
     query = query.Where(translatedExpression); 

    var finalQuery = from item in query.AsEnumerable() 
        select new CustomerModel() 
         { 
          FirstName = item.FirstName, 
          LastName = item.LastName, 
          Id = item.Id, 
          BirthDate = item.BirthDate, 
          CustomerTypeId = item.CustomerTypeId, 
          HasEvenId = (item.Id % 2) == 0 
         }; 

    return finalQuery.AsQueryable(); 
} 

wo:

  • ODataFilterExtractor eine Klasse, die die $ Filterausdruck aus dem RequestMessage extrahieren wir empfangen;
  • ParameterChangeVisitor ändert nur alle ParameterExpression in eine neue mit der angegebenen Zeichenfolge als Parametername;

Außerdem änderte ich die VisitMember Methode der Antwort oben auf diese Weise verbunden:

protected override Expression VisitMember(MemberExpression node) 
{ 
    // if we see x.Name on the old type, substitute for new type 
    if (node.Member.DeclaringType == _from) 
    { 
     MemberInfo toMember = _to.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault(); 
     if (toMember != null) 
     { 
      return Expression.MakeMemberAccess(Visit(node.Expression), toMember); 
     } 
     else 
     { 
      if (_substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Any(me => me.Member.Equals(node.Member))) 
      { 
       MemberExpression key = _substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Single(me => me.Member.Equals(node.Member)); 
       Expression value = _substitutions[key]; 

       // What to return here? 
       return Expression.Invoke(value); 
      } 
     } 
    } 
    return base.VisitMember(node); 
} 

Dank für das Ihnen helfen.

+0

Nun, du bist wirklich mutig! Implementieren eines Linq-Anbieters alle selbst ... – Aliostad

+0

Konnten Sie Ihren wolligen Satz umwandeln _Das Problem ist (...), EF4._ zu einer klaren, prägnanten Frage abzufragen? Markieren Sie den Code wo Sie Hilfe benötigen? –

+0

ich denke, ein Besucher die Arbeit machen könnte – Proviste

Antwort

4

nahm ich die Freiheit des Codes nur ein Haar zu modifizieren, aber dies funktioniert der Trick,

public class Customer 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Id { get; set; } 
    public DateTime? BirthDate { get; set; } 
    public int CustomerTypeId { get; set; } 
} 

public class CustomerModel 
{ 
    public string FullName { get; set; } 
    public bool HasEvenId { get; set; } 
} 

sealed class AToBConverter<TA, TB> : ExpressionVisitor 
{ 
    private readonly Dictionary<ParameterExpression, ParameterExpression> _parameters = new Dictionary<ParameterExpression, ParameterExpression>(); 
    private readonly Dictionary<MemberInfo, LambdaExpression> _mappings; 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     if (node.Type == typeof(TA)) 
     { 
      ParameterExpression parameter; 
      if (!this._parameters.TryGetValue(node, out parameter)) 
      { 
       this._parameters.Add(node, parameter = Expression.Parameter(typeof(TB), node.Name)); 
      } 
      return parameter; 
     } 
     return node; 
    } 

    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (node.Expression == null || node.Expression.Type != typeof(TA)) 
     { 
      return base.VisitMember(node); 
     } 
     Expression expression = this.Visit(node.Expression); 
     if (expression.Type != typeof(TB)) 
     { 
      throw new Exception("Whoops"); 
     } 
     LambdaExpression lambdaExpression; 
     if (this._mappings.TryGetValue(node.Member, out lambdaExpression)) 
     { 
      return new SimpleExpressionReplacer(lambdaExpression.Parameters.Single(), expression).Visit(lambdaExpression.Body); 
     } 
     return Expression.Property(
      expression, 
      node.Member.Name 
     ); 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     return Expression.Lambda(
      this.Visit(node.Body), 
      node.Parameters.Select(this.Visit).Cast<ParameterExpression>() 
     ); 
    } 

    public AToBConverter(Dictionary<MemberInfo, LambdaExpression> mappings) 
    { 
     this._mappings = mappings; 
    } 
} 

sealed class SimpleExpressionReplacer : ExpressionVisitor 
{ 
    private readonly Expression _replacement; 
    private readonly Expression _toFind; 

    public override Expression Visit(Expression node) 
    { 
     return node == this._toFind ? this._replacement : base.Visit(node); 
    } 

    public SimpleExpressionReplacer(Expression toFind, Expression replacement) 
    { 
     this._toFind = toFind; 
     this._replacement = replacement; 
    } 
} 

class Program 
{ 
    private static Dictionary<MemberInfo, LambdaExpression> GetMappings() 
    { 
     var mappings = new Dictionary<MemberInfo, LambdaExpression>(); 
     var mapping = GetMappingFor(model => model.HasEvenId, customer => (customer.Id % 2) == 0); 
     mappings.Add(mapping.Item1, mapping.Item2); 
     mapping = GetMappingFor(model => model.FullName, customer => customer.FirstName + " " + customer.LastName); 
     mappings.Add(mapping.Item1, mapping.Item2); 
     return mappings; 
    } 

    private static Tuple<MemberInfo, LambdaExpression> GetMappingFor<TValue>(Expression<Func<CustomerModel, TValue>> fromExpression, Expression<Func<Customer, TValue>> toExpression) 
    { 
     return Tuple.Create(((MemberExpression)fromExpression.Body).Member, (LambdaExpression)toExpression); 
    } 

    static void Main() 
    { 
     Expression<Func<CustomerModel, bool>> source = model => model.HasEvenId && model.FullName == "John Smith"; 
     Expression<Func<Customer, bool>> desiredResult = model => (model.Id % 2) == 0 && (model.FirstName + " " + model.LastName) == "John Smith"; 
     Expression output = new AToBConverter<CustomerModel, Customer>(GetMappings()).Visit(source); 
     Console.WriteLine("The two expressions do {0}match.", desiredResult.ToString() == output.ToString() ? null : "not "); 
     Console.ReadLine(); 
    } 
} 
+0

Ich werde den Code dieses Wochenende versuchen :) – Kralizek

+0

Wie Sie im Code sehen können, fügte ich der Model-Klasse eine FullName-Eigenschaft hinzu, nur um die Beziehungszuordnung noch weiter zu testen. Ich habe dem Besucher auch die Freiheit genommen, ihn so zu codieren, dass jede andere Eigenschaft in beiden existiert (dh wenn Sie dem CustomerModel eine Id-Eigenschaft hinzugefügt haben, dann würde jede Abfrage der CustomerModel-Id-Eigenschaft einfach als eine Abfrage auf die Kunden-ID übersetzt werden es gibt keine Zuordnung speziell für diese Eigenschaft) –

+0

Vielen Dank dafür. Durch die Kombination von Code mit dem Code aus http://stackoverflow.com/questions/18365764/implement-iqueryable-wrapper-to-translate-result-objects, konnte ich eine OData-Schicht auf einer massiven vorbestehenden schaffen Datenbank. Es hat ein wenig mehr Arbeit in Anspruch genommen, IQeryable-Objekte zu übersetzen, die aus Repository-Musterklassen erstellt wurden, und sie an OData zu übergeben, aber es funktioniert. Die Verwendung herkömmlicher Aufrufe der Projektion und/oder '.ToList()' führte zu OOM-Fehlern. Ihr Ansatz ermöglicht eine sehr späte Bindung, so dass Objekte nur übersetzt werden, wenn OData sie spezifisch anfordert. – user326608

3

Eine andere Lösung wäre AutoMapper zu verwenden komplexe Typen abzubilden und die resultierende Ausdruck Abfrage mit einem ExpressionTransformer, bevor es ändern wird ausgeführt. Ich werde versuchen, mit einer vollständigen Probe zu erklären:

Modellklassen

Einige POCO Klassen nur zum Halten von Daten.

public enum CostUnitType 
{ 
    None = 0, 
    StockUnit = 1, 
    MachineUnit = 2, 
    MaintenanceUnit = 3 
} 

public class CostUnit 
{ 
    public string CostUnitId { get; set; } 
    public string WorkplaceId { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 

    public CostUnitType Type { get; set; } 

    public override string ToString() 
    { 
     return CostUnitId + " " + Name + " " + Type; 
    } 
} 

public class StockUnit 
{ 
    public string Id { get; set; } 
    public string Name { get; set; } 
} 

public class MachineUnit 
{ 
    public string Id { get; set; } 
    public string Name { get; set; } 
} 

public class MaintenanceUnit 
{ 
    public string Id { get; set; } 
    public string Name { get; set; } 
} 

Die ExpressionTransformer Klasse

Die meiste Zeit, mit der Mapper statische Klasse ist in Ordnung, aber manchmal müssen Sie die gleichen Typen mit unterschiedlichen Konfigurationen auf der Karte, so dass Sie explizit eine IMappingEngine verwenden müssen wie folgt:

var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); 
var engine = new MappingEngine(configuration); 

Der Weg, eine MappingEngine zu erstellen, ist überhaupt nicht offensichtlich. Ich musste den Quellcode eingraben, um herauszufinden, wie es gemacht wurde.

public static class ExpressionTransformer 
{ 
    private static readonly MappingEngine Mapper; 

    /// <summary> 
    /// Initializes the <see cref="ExpressionTransformer"/> class. 
    /// Creates an instance of AutoMapper. Initializes mappings. 
    /// </summary> 
    static ExpressionTransformer() 
    { 
     ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); 

     // Create mapping 
     // Maps Id from StockUnit to CostUnitId from CostUnit 
     configurationStore.CreateMap<StockUnit, CostUnit>() 
      .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id)); 
     // Maps Id from MachineUnit to CostUnitId from CostUnit 
     configurationStore.CreateMap<MachineUnit, CostUnit>() 
      .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id)); 
     // Maps Id from MaintenanceUnit to CostUnitId from CostUnit 
     configurationStore.CreateMap<MaintenanceUnit, CostUnit>() 
      .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id)); 

     // Create instance of AutoMapper 
     Mapper = new MappingEngine(configurationStore); 
    } 

    public static Expression<Func<TDestination, bool>> Tranform<TSource, TDestination>(Expression<Func<TSource, bool>> sourceExpression) 
    { 
     // Resolve mappings by AutoMapper for given types. 
     var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination)); 

     if (map == null) 
     { 
      throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name)); 
     } 

     // Transform from TSource to TDestination with specified mappings 
     var visitor = new ParameterTypeVisitor<TSource, TDestination>(sourceExpression, map.GetPropertyMaps()); 
     var expression = visitor.Transform(); 

     return expression; 
    } 

    private class ParameterTypeVisitor<TSource, TDestination> : ExpressionVisitor 
    { 
     private readonly Dictionary<string, ParameterExpression> _parameters; 
     private readonly Expression<Func<TSource, bool>> _expression; 
     private readonly IEnumerable<PropertyMap> _maps; 

     public ParameterTypeVisitor(Expression<Func<TSource, bool>> expression, IEnumerable<PropertyMap> maps) 
     { 
      _parameters = expression.Parameters 
       .ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name)); 

      _expression = expression; 

      _maps = maps; 
     } 

     public Expression<Func<TDestination, bool>> Transform() 
     { 
      return (Expression<Func<TDestination, bool>>) Visit(_expression); 
     } 

     protected override Expression VisitMember(MemberExpression node) 
     { 
      if (node.Member.DeclaringType == typeof(TSource)) 
      { 
       var memberName = node.Member.Name; 
       var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type 
                 && p.SourceMember.Name == memberName); 
       if (member != null) 
       { 
        // Return Property from TDestination 
        var expression = Visit(node.Expression); 
        return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo); 
       } 
      } 

      return base.VisitMember(node); 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      var parameter = _parameters[node.Name]; 
      return parameter; 
     } 

     protected override Expression VisitLambda<T>(Expression<T> node) 
     { 
      var expression = Visit(node.Body); 
      return Expression.Lambda(expression, _parameters.Select(x => x.Value)); 
     } 
    } 
} 

Nutzungs

Um einen Ausdruck zu konvertieren müssen wir nur Methode der ExpressionTransformer Transform-Klasse rufen

  Expression<Func<StockUnit, bool>> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC"; 

      // Call Transform<TSource, TDestination> method. 
      Expression<Func<CostUnit, bool>> costUnitQuery = ExpressionTransformer.Tranform<StockUnit, CostUnit>(stockQuery); 

Ergebnis

Resulting expressions

Verwandte Themen