用linq表达式的协变/逆变

我有一个名为“CreateCriteriaExpression”的函数,它接受一个json字符串并从中创建一个linq表达式。

此方法由另一个名为“GetByCriteria”的方法调用,该方法调用“CreateCriteriaExpression”方法,然后针对实体框架上下文执行该表达式。

对于我所有的实体框架对象,除了它的类型之外,“GetByCriteria”方法是相同的。 所以我试图将其转换为使用泛型而不是硬编码类型。

当“GetByCriteria”方法达到它必须调用“CreateCriteriaExpression”方法时,我使用工厂类来确定要使用的适当的类/方法。 然后在“linq表达式”类中,创建并返回特定类型的linq表达式。

我遇到的问题是,必须为特定类型创建linq表达式,但返回值是泛型类型的,并且它不会自动在两者之间进行转换,即使其中一个是另一个的父项(协方差)。

有什么办法可以使这项工作?

一些示例代码:

“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;
    }

JsonLinqParser类(不构建):

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;
        }
    }
}

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;
    }
}

工厂类:

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);
            }
        }
    }
}

问题是JsonLinqParser_Paser的签名是泛型的,类型不可知的,而你的实现是它对于具体的BestAvailableFIP类型的专门化。 这不是一个协变问题,它只是键入不兼容性(在编译器级别)。

解决方案是使JsonLinqParser成为泛型类型(不具有泛型方法) - 甚至是接口,然后使JsonLinqParser_Paser实现JsonLinqParser<BestAvailableFIP> 。 然后我们会将所有内容匹配起来

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)
}

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
    }
}

工厂需要返回IJsonLinqParser<TEntity> ,因为我们知道TEntity所以没什么问题:

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);
        }
    }
}

最后在GetByCriteria你可以有:

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

现在在解析器方法调用中不需要<TEntity> ,因为解析器已经是TEntity特定的。

希望这可以帮助。

顺便说一句,您的工厂基础设施可以很容易地被良好的IoC工具取代。

链接地址: http://www.djcxy.com/p/54133.html

上一篇: Covariance/Contravariance with a linq expression

下一篇: app Purchase Item after app available on App store without updating app