2011-08-01 15 views
7

Tengo una función llamada "CreateCriteriaExpression" que toma una cadena json y crea una expresión linq a partir de ella.Covarianza/Contravarianza con una expresión linq

Este método es llamado por otro llamado "GetByCriteria", que llama al método "CreateCriteriaExpression" y luego ejecuta esa expresión en un contexto de entidad de marco.

Para todos mis objetos de entidad de marco, el método "GetByCriteria" es idéntico excepto por sus tipos. Así que estoy tratando de convertirlo para usar genéricos en lugar de tipos codificados.

Cuando el método "GetByCriteria" llega al punto en el que tiene que llamar al método "CreateCriteriaExpression", lo estoy usando una clase de fábrica para determinar la clase/método apropiado para usar. Luego, en la clase "expresión de linq", se crea y devuelve la expresión de linq para un tipo particular.

El problema que tengo es que la expresión linq tiene que crearse para un tipo específico, pero el valor devuelto es del tipo genérico y no se convertirá automáticamente entre los dos, aunque uno es uno de los padres del otro (covarianza).

¿Hay alguna manera de que pueda hacer esto?

un código de ejemplo:

El método "GetByCriteria":

/// <summary> 
    /// Gets a <see cref="System.Collections.Generic.List"/> of <see cref="TEntity"/> 
    /// objects that match the passed JSON string. 
    /// </summary> 
    /// <param name="myCriteria">A list of JSON strings containing a key/value pair of "parameterNames" and "parameterValues".</param> 
    /// <param name="myMatchMethod">Defines which matching method to use when finding matches on the <paramref name="myCriteria"/>.</param> 
    /// <returns> 
    /// A <see cref="System.Collections.Generic.List"/> of <see cref="TEntity"/> 
    /// objects. 
    /// </returns> 
    /// <seealso cref="TEntity"/> 
    /// 
    /// <seealso cref="Common.MultipleCriteriaMatchMethod"/> 
    /// <remarks> 
    /// This method takes a <see cref="System.Collections.Generic.List"/> of JSON strings, and a <see cref="Common.MultipleCriteriaMatchMethod"/> and returns a 
    /// <see cref="System.Collections.Generic.List"/> of all matching 
    /// <see cref="TEntity"/> objects from the back-end database. The <paramref name="myMatchMethod"/> is used to determine how to match when multiple <paramref name="myCriteria"/> are passed. You can require that any results must match on ALL the passed JSON criteria, or on ANY of the passed criteria. This is essentially an "AND" versus and "OR" comparison. 
    /// </remarks> 
    [ContractVerification(true)] 
    public static List<TEntity> GetByCriteria<TContext, TEntity>(List<string> myCriteria, Common.MultipleCriteriaMatchMethod myMatchMethod) 
     where TContext : System.Data.Objects.ObjectContext, new() 
     where TEntity : System.Data.Objects.DataClasses.EntityObject 
    { 
     // Setup Contracts 
     Contract.Requires(myCriteria != null); 

     TContext db = new TContext(); 


     // Intialize return variable 
     List<TEntity> result = null; 

     // Initialize working variables 
     // Set the predicates to True by default (for "AND" matches) 
     var predicate = PredicateBuilder.True<TEntity>(); 
     var customPropertiesPredicate = PredicateBuilder.True<TEntity>(); 

     // Set the predicates to Falase by default (for "OR" matches) 
     if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAny) 
     { 
      predicate = PredicateBuilder.False<TEntity>(); 
      customPropertiesPredicate = PredicateBuilder.False<TEntity>(); 
     } 


     // Loop over each Criteria object in the passed list of criteria 
     foreach (string x in myCriteria) 
     { 
      // Set the Criteria to local scope (sometimes there are scope problems with LINQ) 
      string item = x; 
      if (item != null) 
      { 
       JsonLinqParser parser = JsonLinqParserFactory.GetParser(typeof(TEntity)); 

       // If the designated MultipleCriteriaMatchMethod is "MatchOnAll" then use "AND" statements 
       if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAll) 
       { 
        predicate = predicate.Expand().And<TEntity>(parser.CreateCriteriaExpression<TEntity>(item).Expand()); 
        customPropertiesPredicate = customPropertiesPredicate.Expand().And<TEntity>(parser.CreateCriteriaExpressionForCustomProperties<TEntity>(item).Expand()); 
       } 
       // If the designated MultipleCriteriaMatchMethod is "MatchOnAny" then use "OR" statements 
       else if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAny) 
       { 
        predicate = predicate.Expand().Or<TEntity>(parser.CreateCriteriaExpression<TEntity>(item).Expand()); 
        customPropertiesPredicate = customPropertiesPredicate.Expand().Or<TEntity>(parser.CreateCriteriaExpressionForCustomProperties<TEntity>(item).Expand()); 
       } 
      } 
     } 

     // Set a temporary var to hold the results 
     List<TEntity> qry = null; 

     // Set some Contract Assumptions to waive Static Contract warnings on build 
     Contract.Assume(predicate != null); 
     Contract.Assume(customPropertiesPredicate != null); 


     // Run the query against the backend database 
     qry = db.CreateObjectSet<TEntity>().AsExpandable<TEntity>().Where<TEntity>(predicate).ToList<TEntity>(); 
     //qry = db.CreateObjectSet<TEntity>().Where(predicate).ToList<TEntity>(); 
     // Run the query for custom properties against the resultset obtained from the database 
     qry = qry.Where<TEntity>(customPropertiesPredicate.Compile()).ToList<TEntity>(); 

     // Verify that there are results 
     if (qry != null && qry.Count != 0) 
     { 
      result = qry; 
     } 

     // Return the results 
     return result; 
    } 

La clase JsonLinqParser (no construye):

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using LinqKit; 
using Newtonsoft.Json.Linq; 

namespace DAL 
{ 
    internal class JsonLinqParser_Paser : JsonLinqParser 
    { 
     internal override System.Linq.Expressions.Expression<Func<TEntity, bool>> CreateCriteriaExpression<TEntity>(string myCriteria) 
     { 
      var predicate = PredicateBuilder.True<BestAvailableFIP>(); 

      JObject o = JObject.Parse(myCriteria); 

      // bmp 
      decimal _bmp; 
      if (o["bmp"] != null && decimal.TryParse((string)o["bmp"], out _bmp)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.bmp == _bmp); 
      } 
      // COUNTY 
      if (!string.IsNullOrWhiteSpace((string)o["COUNTY"])) 
      { 
       string _myStringValue = (string)o["COUNTY"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.COUNTY.Contains(_myStringValue)); 
      } 
      // emp 
      decimal _emp; 
      if (o["emp"] != null && decimal.TryParse((string)o["emp"], out _emp)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.emp == _emp); 
      } 
      // FIPSCO_STR 
      if (!string.IsNullOrWhiteSpace((string)o["FIPSCO_STR"])) 
      { 
       string _myStringValue = (string)o["FIPSCO_STR"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.FIPSCO_STR.Contains(_myStringValue)); 
      } 
      // FIPSCODE 
      double _FIPSCODE; 
      if (o["FIPSCODE"] != null && double.TryParse((string)o["FIPSCODE"], out _FIPSCODE)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.FIPSCODE == _FIPSCODE); 
      } 
      // FROMDESC 
      if (!string.IsNullOrWhiteSpace((string)o["FROMDESC"])) 
      { 
       string _myStringValue = (string)o["FROMDESC"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.FROMDESC.Contains(_myStringValue)); 
      } 
      // LANEMI 
      decimal _LANEMI; 
      if (o["LANEMI"] != null && decimal.TryParse((string)o["LANEMI"], out _LANEMI)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.LANEMI == _LANEMI); 
      } 
      // MPO_ABBV 
      if (!string.IsNullOrWhiteSpace((string)o["MPO_ABBV"])) 
      { 
       string _myStringValue = (string)o["MPO_ABBV"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.MPO_ABBV.Contains(_myStringValue)); 
      } 
      // owner 
      if (!string.IsNullOrWhiteSpace((string)o["owner"])) 
      { 
       string _myStringValue = (string)o["owner"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.owner.Contains(_myStringValue)); 
      } 
      // PASER 
      decimal _PASER; 
      if (o["PASER"] != null && decimal.TryParse((string)o["PASER"], out _PASER)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.PASER == _PASER); 
      } 
      // PASER_GROUP 
      if (!string.IsNullOrWhiteSpace((string)o["PASER_GROUP"])) 
      { 
       string _myStringValue = (string)o["PASER_GROUP"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.PASER_GROUP.Contains(_myStringValue)); 
      } 
      // pr 
      decimal _pr; 
      if (o["pr"] != null && decimal.TryParse((string)o["pr"], out _pr)) 
      { 
       predicate = predicate.And<BestAvailableFIP>(x => x.pr == _pr); 
      } 
      // RDNAME 
      if (!string.IsNullOrWhiteSpace((string)o["RDNAME"])) 
      { 
       string _myStringValue = (string)o["RDNAME"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.RDNAME.Contains(_myStringValue)); 
      } 
      // SPDR_ABBV 
      if (!string.IsNullOrWhiteSpace((string)o["SPDR_ABBV"])) 
      { 
       string _myStringValue = (string)o["SPDR_ABBV"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.SPDR_ABBV.Contains(_myStringValue)); 
      } 
      // TODESC 
      if (!string.IsNullOrWhiteSpace((string)o["TODESC"])) 
      { 
       string _myStringValue = (string)o["TODESC"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.TODESC.Contains(_myStringValue)); 
      } 
      // TYPE 
      if (!string.IsNullOrWhiteSpace((string)o["TYPE"])) 
      { 
       string _myStringValue = (string)o["TYPE"]; 
       predicate = predicate.And<BestAvailableFIP>(x => x.TYPE.Contains(_myStringValue)); 
      } 

      return predicate; 
     } 

     internal override System.Linq.Expressions.Expression<Func<TEntity, bool>> CreateCriteriaExpressionForCustomProperties<TEntity>(string myCriteria) 
     { 
      var predicate = PredicateBuilder.True<TEntity>(); 

      return predicate; 
     } 
    } 
} 

La clase base JsonLinqParser:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Linq.Expressions; 

namespace DAL 
{ 
    abstract class JsonLinqParser 
    { 
     abstract internal Expression<Func<TEntity, bool>> CreateCriteriaExpression<TEntity>(string myCriteria) 
      where TEntity : System.Data.Objects.DataClasses.EntityObject; 
     abstract internal Expression<Func<TEntity, bool>> CreateCriteriaExpressionForCustomProperties<TEntity>(string myCriteria) 
      where TEntity : System.Data.Objects.DataClasses.EntityObject; 
    } 
} 

La clase de fábrica:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace DAL 
{ 
    internal static class JsonLinqParserFactory 
    { 
     internal static JsonLinqParser GetParser(Type type) 
     { 
      switch (type.Name) 
      { 
       case "BestAvailableFIP": 
        return new JsonLinqParser_Paser(); 
       default: 
        //if we reach this point then we failed to find a matching type. Throw 
        //an exception. 
        throw new Exception("Failed to find a matching JsonLinqParser in JsonLinqParserFactory.GetParser() - Unknown Type: " + type.Name); 
      } 
     } 
    } 
} 

Respuesta

4

El problema es que las firmas JsonLinqParser_Paser 's son genéricas, el tipo agnóstica, y su aplicación es su especialización para el hormigón BestAvailableFIP tipo. Esto no es un problema de covarianza, solo es incompatibilidad de tipo (a nivel de compilador).

La solución es hacer que JsonLinqParser sea de tipo genérico (sin tener métodos genéricos) - o incluso una interfaz, luego haga JsonLinqParser_Paser para implementar JsonLinqParser<BestAvailableFIP>. Entonces tendremos todo lo que coincida.

La interfaz IJsonLinqParser:

interface IJsonLinqParser<TEntity> 
    where TEntity : System.Data.Objects.DataClasses.EntityObject 
{ 
    Expression<Func<TEntity, bool>> CreateCriteriaExpression(string myCriteria); 
    Expression<Func<TEntity, bool>> CreateCriteriaExpressionForCustomProperties(string myCriteria) 
} 

La implementación - firmas para JsonLinqParser_Paser:

internal class JsonLinqParser_Paser : IJsonLinqParser<BestAvailableFIP> 
{ 
    public Expression<Func<BestAvailableFIP, bool>> CreateCriteriaExpression(string myCriteria) 
    { 
     // implementation as yours 
    } 

    public Expression<Func<BestAvailableFIP, bool>> CreateCriteriaExpressionForCustomProperties(string myCriteria) 
    { 
     // implementation as yours 
    } 
} 

La fábrica necesita para volver IJsonLinqParser<TEntity>, lo que no es un problema, ya que sabemos TEntity allí:

internal static class JsonLinqParserFactory 
{ 
    internal static IJsonLinqParser<TEntity> GetParser<TEntity>() 
     where TEntity : System.Data.Objects.DataClasses.EntityObject 
    { 
     switch (typeof(TEntity).Name) 
     { 
      case "BestAvailableFIP": 
       return (IJsonLinqParser<TEntity>) new JsonLinqParser_Paser(); 
      default: 
       //if we reach this point then we failed to find a matching type. Throw 
       //an exception. 
       throw new Exception("Failed to find a matching JsonLinqParser in JsonLinqParserFactory.GetParser() - Unknown Type: " + typeof(TEntity).Name); 
     } 
    } 
} 

Y finalmente en GetByCriteria se puede tener:

IJsonLinqParser<TEntity> parser = JsonLinqParserFactory.GetParser<TEntity>(); 

No hay necesidad de <TEntity> en analizador método llama ahora, como ya está analizador específico de TEntity.

Espero que esto ayude.

Por cierto, la infraestructura de su fábrica puede ser reemplazada fácilmente por una buena herramienta IoC.

+0

Gracias. Tengo esto para trabajar! Buscaré en IoC. Había leído sobre esto antes, pero poner mi cabeza alrededor era un poco más de lo que tenía tiempo. Puede valer la pena volver a visitar. –

Cuestiones relacionadas