Ковариантность / Контравариантность с выражением linq - PullRequest
7 голосов
/ 01 августа 2011

У меня есть функция с именем «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);
            }
        }
    }
}

1 Ответ

4 голосов
/ 02 августа 2011

Проблема в том, что сигнатуры 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 инструментом.

...