Metoda de extensie IQueryable pentru linq2entities

Încerc să pun în aplicare o metodă de extensie care va funcționa cu linq2entities. Initial, am presupus ca daca metoda mea de extensie mi-a luat si am returnat un IQueryable, si atata timp cat expresia mea ar fi folosit doar metode acceptate, atunci ar fi bine. Aveam multe probleme, ca o ultimă soluție, am copiat o metodă existentă de extensie .NET pe care am știut să o lucrez (FirstOrDefault) și pur și simplu am redenumit-o. Se pare că va evalua validarea "nu poate fi tradusă într-o expresie a magazinului" pe baza expresiei returnate de la metodă, și nu a metodei în sine.

var prs = db.People.Where(p => p.PersonKey == 15).Select(p =>
  new
  {
    id = p.PersonKey,
    name1 = p.PersonHistories.AsQueryable().AsOf().Name
  } 
).ToList();

Metoda extensiei mele, care este doar o copie a FirstOrDefault pe care am redenumit-o:

public static TSource AsOf(this IQueryable source)
{
  return source.Provider.Execute(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression }));
}

Eroare:

LINQ to Entities does not recognize the method 
'Models.PersonHistory AsOf[PersonHistory](System.Linq.IQueryable`1[Models.PersonHistory])' 
method, and this method cannot be translated into a store expression.

Cum pun în aplicare o metodă de extensie IQueryable care este acceptată în Linq2Entities?

What I really want AsOf(source, DateTime asOf) to do is soemthing like source.FirstOrDefault(s => s.EndDate > asOf && asOf >= s.StartDate ), but I'm not sure how to accomplish this so that it is supported by linq2entities.

LINQKIT: This is what I've come up with using linqkit and I'm hoping somehow I can factor this into something more reusable:

Expression> IsCurrent = (p) => p.Ends > DateTime.Now && p.Starts <= DateTime.Now;
var query = db.PersonHistories.Where(IsCurrent);
  1. Au o expresie mai declarată la nivel global, în loc de o locală variabilă.
  2. Adăugați un parametru DateTime asOf, în loc de a avea codul .Now hard codat.
  3. Și dacă este posibil, adaptați-l într-o metodă de extensie (acest tip de este identic cu # 1, doar că o metodă de extensie este ideală.
6
Ei bine, dacă cineva are un exemplu despre modul în care aș putea să extind furnizorul de interogare, aș fi dispus să încerc. Pentru a răspunde la întrebarea dvs., am presupus că nu va încerca să traducă metoda mea (ce este într-un nume?), Ci să traducă expresia, pe care o cunoaște cum să facă, deoarece expresia nu ar folosi nimic familiar pentru a lega entitățile.
adăugat autor AaronLS, sursa
Evident, găsesc o grămadă de chestii, dar așa am ajuns să pierd o zi încercând să obținem acest lucru doar pentru a realiza toate exemplele de extensii IQueryable care nu funcționează cu linq2entities ... Deci, orice orientare ar fi apreciată.
adăugat autor AaronLS, sursa
Dar cum ObjectQueryProvider ar ști cum să traduceți metoda în SQL? O metodă de extensie care poate fi interogată duce doar metoda la furnizorul de interogări curent, care o execută efectiv. Deci, va trebui să extindeți furnizorul de interogări (dacă este posibil) sau să scrieți propria dvs. după o după-amiază ploioasă :).
adăugat autor Gert Arnold, sursa
Poate doriți să aruncați o privire la re-linq , care are scopul de a scăpa din IQueryable lume și într-un arbore abstract de sintaxă care este de fapt compozabil (expresiile sunt imuabile). Există un link către un eșantion de pe CodeProject, astfel încât, sperăm, să puteți începe să mergeți în direcția cea bună.
adăugat autor SPFiredrake, sursa

3 răspunsuri

Te-am vazut comentand raspunsul meu la o alta intrebare, asa ca m-am gandit sa raspund si aici. Am făcut unele modificări și îmbunătățiri ale codului (suport pentru interogări compilate și metode de extensie personalizată pentru înlocuirea expresiei).

Acest lucru ar putea servi drept răspuns:

/// 
/// Type helpers ///
 
internal static class TypeSystem
{
    private static Type FindIEnumerable(Type seqType)
    {
        Type type;
        if (seqType == null || seqType == typeof(string) || seqType == typeof(byte[]))
        {
            return null;
        }
        else
        {
            if (!seqType.IsArray)
            {
                if (seqType.IsGenericType)
                {
                    Type[] genericArguments = seqType.GetGenericArguments();
                    int num = 0;
                    while (num < (int)genericArguments.Length)
                    {
                        Type type1 = genericArguments[num];
                        Type[] typeArray = new Type[1];
                        typeArray[0] = type1;
                        Type type2 = typeof(IEnumerable<>).MakeGenericType(typeArray);
                        if (!type2.IsAssignableFrom(seqType))
                        {
                            num++;
                        }
                        else
                        {
                            type = type2;
                            return type;
                        }
                    }
                }
                Type[] interfaces = seqType.GetInterfaces();
                if (interfaces != null && (int)interfaces.Length > 0)
                {
                    Type[] typeArray1 = interfaces;
                    int num1 = 0;
                    while (num1 < (int)typeArray1.Length)
                    {
                        Type type3 = typeArray1[num1];
                        Type type4 = TypeSystem.FindIEnumerable(type3);
                        if (type4 == null)
                        {
                            num1++;
                        }
                        else
                        {
                            type = type4;
                            return type;
                        }
                    }
                }
                if (!(seqType.BaseType != null) || !(seqType.BaseType != typeof(object)))
                {
                    return null;
                }
                else
                {
                    return TypeSystem.FindIEnumerable(seqType.BaseType);
                }
            }
            else
            {
                Type[] elementType = new Type[1];
                elementType[0] = seqType.GetElementType();
                return typeof(IEnumerable<>).MakeGenericType(elementType);
            }
        }
    }

    internal static Type GetElementType(Type seqType)
    {
        Type type = TypeSystem.FindIEnumerable(seqType);
        if (type != null)
        {
            return type.GetGenericArguments()[0];
        }
        else
        {
            return seqType;
        }
    }
}

/// 
/// Marks an extension as compatible for custom linq expression expansion /// Optionally if you can not write the extension method to fit your needs, you can provide a /// expression id constant for a registered expression. ///
 
[AttributeUsage(AttributeTargets.Method, AllowMultiple= false, Inherited = false)]
class ExpandableQueryMethodAttribute :
    Attribute
{
    public ExpandableQueryMethodAttribute()
    {
    }
    public ExpandableQueryMethodAttribute(string expressionId)
    {
        _expressionId = expressionId;
    }

    private string _expressionId;
    public LambdaExpression TranslationExpression
    {
        get
        {
            return _expressionId != null ? QueryMethodTranslationExpressions.GetRegistered(_expressionId) : null;
        }
    }
}

/// 
/// Used to register expressions for extension method to expression substitutions ///
 
static class QueryMethodTranslationExpressions
{
    private static Dictionary expressionList = new Dictionary();

    /// 
/// Register expression ///
 
    /// type of expression delegate
    /// 
id constant for use with ExpandableQueryMethodAttribute
    /// 
expression
    public static void RegisterExpression(string id, Expression expr)
    {
        expressionList.Add(id, expr);
    }

    public static LambdaExpression GetRegistered(string id)
    {
        //Extensions;
        return expressionList[id];
    }
}

static class Extensions
{
    /// 
/// Use on object sets before using custom extension methods, except inside compiled queries ///
 
    public static IQueryable AsExtendable(this IQueryable source)
    {
        if (source is ExtendableQuery)
        {
            return (ExtendableQuery)source;
        }

        return new ExtendableQueryProvider(source.Provider).CreateQuery(source.Expression);
    }
}

/// 
/// Provides PlaceHolderQuery /// /// No other functionality ///
 
public class PlaceHolderQueryProvider : IQueryProvider
{
    public PlaceHolderQueryProvider()
    {
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new PlaceHolderQuery(this, expression);
    }

    public IQueryable CreateQuery(Expression expression)
    {
        Type elementType = TypeSystem.GetElementType(expression.Type);
        try
        {
            return (IQueryable)Activator.CreateInstance(typeof(PlaceHolderQuery<>).MakeGenericType(elementType), new object[] { this, expression });
        }
        catch (System.Reflection.TargetInvocationException tie)
        {
            throw tie.InnerException;
        }
    }

    public TResult Execute<tresult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }
}

/// 
/// Does nothing /// /// Acts only as a holder for expression ///
 
public class PlaceHolderQuery : IQueryable, IOrderedQueryable
{

    private Expression _expression;
    private PlaceHolderQueryProvider _provider;

    public PlaceHolderQuery(PlaceHolderQueryProvider provider, Expression expression)
    {
        _provider = provider;
        _expression = expression;
    }

    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public Type ElementType
    {
        get
        {
            return typeof(T);
        }
    }

    public Expression Expression
    {
        get
        {
            return _expression;
        }
    }

    public IQueryProvider Provider
    {
        get
        {
            return _provider;
        }
    }
}

/// 
/// Walks the expression tree and invokes custom extension methods ( to expand them ) or substitutes them with custom expressions ///
 
class ExtendableVisitor : ExpressionVisitor
{
    class ExpandingVisitor : ExpressionVisitor
    {
        private Dictionary _substitutionDictionary;

        public ExpandingVisitor(Dictionary subDict)
        {
            _substitutionDictionary = subDict;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (_substitutionDictionary != null && _substitutionDictionary.ContainsKey(node))
                return _substitutionDictionary[node];
            else
                return base.VisitParameter(node);
        }
    }

    IQueryProvider _provider;

    internal ExtendableVisitor()
    {
        _provider = new PlaceHolderQueryProvider();
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        ExpandableQueryMethodAttribute attrib = (ExpandableQueryMethodAttribute)node.Method.GetCustomAttributes(typeof(ExpandableQueryMethodAttribute), false).FirstOrDefault();

        if (attrib != null && node.Method.IsStatic)
        {

            if (attrib.TranslationExpression != null && attrib.TranslationExpression.Parameters.Count == node.Arguments.Count)
            {
                Dictionary subDict = new Dictionary();

                for (int i = 0; i < attrib.TranslationExpression.Parameters.Count; i++)
                {
                    subDict.Add(attrib.TranslationExpression.Parameters[i], node.Arguments[i]);
                }

                ExpandingVisitor expander = new ExpandingVisitor(subDict);

                Expression exp = expander.Visit(attrib.TranslationExpression.Body);

                return exp;
            }
            else if (typeof(IQueryable).IsAssignableFrom(node.Method.ReturnType))
            {
                object[] args = new object[node.Arguments.Count];
                args[0] = _provider.CreateQuery(node.Arguments[0]);

                for (int i = 1; i < node.Arguments.Count; i++)
                {
                    Expression arg = node.Arguments[i];
                    args[i] = (arg.NodeType == ExpressionType.Constant) ? ((ConstantExpression)arg).Value : arg;
                }

                Expression exp = ((IQueryable)node.Method.Invoke(null, args)).Expression;

                return exp;
            }
        }            

        return base.VisitMethodCall(node);
    }
}

/// 
/// Used for query compilation /// /// If custom extension methods are used, the existing CompileQuery functions do not work, so I had to write this. ///
 
static class CompiledExtendableQuery
{
    public static Func
               Compile(
       Expression> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func
               Compile(
       Expression> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func
               Compile
      (Expression> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func
               Compile(
       Expression> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func
               Compile(
       Expression> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func
               Compile
3
adăugat
Ați folosit acest lucru cu o metodă de extensie care returnează un singur element, echivalentul lui. First or .Single? M-am străduit să vă adaptez codul pentru a face acest lucru, astfel încât o metodă să-i poată exprima expresia, dar apoi am găsit Expresia de traducere, care cred că este exact ceea ce am nevoie. Problema este că nu știu ce să trec pentru a înregistra expresia, deoarece expresia pe care o am depinde de existența anumitor parametri: pastebin.com/CpY9Sg65
adăugat autor AaronLS, sursa
P.S. Aceasta este ceea ce am încercat, dar primesc eroarea "Expresia tipului" System.Linq.Expressions.MethodCallExpression "nu poate fi folosită pentru tipul returnat" PropertyHistory "": pastebin.com/BzQWqJRY
adăugat autor AaronLS, sursa
Niciodată nu am făcut-o mai complicată decât trebuia să fie. aceasta funcționează: QueryMethodTranslationExpressions.RegisterExpression , PropertyHistory >> ("1", source => source.FirstOrDefault (xp => xp.Ends> DateTime.Now && xp . Începeți <= DateTime.Now));
adăugat autor AaronLS, sursa
@AaronLS Mă bucur să știu că ați reușit să lucrați.
adăugat autor Lord Terabyte, sursa

Acest lucru ar trebui să funcționeze fără a vă extinde furnizorul de interogări. În esență, o sparge exact la ceea ce funcționează IsCurrent func.

public static class IQueryableExtensions
{
    public static IQueryable IsCurrent(this IQueryable query,
        Expression expressionEnd,
        Expression expressionStart,
        DateTime asOf) where T : class
    {
       //Lambdas being sent in
        ParameterExpression paramEnd = expressionEnd.Parameters.Single();
        ParameterExpression paramStart = expressionStart.Parameters.Single();

       //GT Comparison
        BinaryExpression expressionGT = ExpressionGT(expressionEnd.Body, asOf);

       //LT Comparison
        BinaryExpression expressionLT = ExpressionLE(expressionStart.Body, asOf);

        query = query.Where(Expression.Lambda>(expressionGT, paramEnd))
                     .Where(Expression.Lambda>(expressionLT, paramStart));

        return query;
    }

    private static BinaryExpression ExpressionLE(Expression body, DateTime value)
    {
        return Expression.LessThanOrEqual(body, Expression.Constant(value, typeof(DateTime)));
    }

    private static BinaryExpression ExpressionGT(Expression body, DateTime value)
    {
        return Expression.GreaterThan(body, Expression.Constant(value, typeof(DateTime)));
    }
}

Și să o folosești

var query = db.PersonHistories.IsCurrent( p => p.Ends, 
                                          p => p.Starts, 
                                          DateTime.Now );
2
adăugat
Acest lucru funcționează, cu excepția unor locuri cum ar fi exemplul meu (primul bloc de cod în cauză) unde acesta este imbricat într-o interogare, unde îi dă linq2entities nu recunoaște eroarea de metodă. Singurul motiv pentru care funcționează LinqKit este faptul că ei și-au implementat propriile ExpressionVisitors, care este în esență ceea ce Gert spune că trebuie să fac, este doar un cod foarte complicat de înțeles.
adăugat autor AaronLS, sursa
@Steve That. În cazul în care ar fi repetat în mai multe locuri, așa că am vrut să incapsulate această logică într-o metodă extensie. Apoi, următorul pas ar fi să-l abrogem să opereze pe un IHistory în loc de PersonalHistory, astfel încât orice obiect al implementării IHistory să poată fi ușor despicat pentru un punct-în-timp. Am gasit o solutie pe care cred ca o vom putea face: pastebin.com/4fMjaCMV
adăugat autor AaronLS, sursa
@Steve În esență, ceea ce se întâmplă este apelurile de la vistior. Invocați metoda mea de extensie, care apoi returnează IQueryable care înlocuiește apelul metodei de extensie în arborele epxresison. Aceasta a fost ceea ce încercam să fac cu codul lui LinqKit, dar nu am putut să sun apelul de invocare. Lucrez la niște legături și voi posta soluția completă.
adăugat autor AaronLS, sursa
EF nu acceptă acest lucru fără a modifica parserul Linq care generează SQL. Puteți săturați în curajul lui LinqKit, dar este complicat. Mi-e teamă că dacă vrei să faci limite inline pe o sub-selectare va trebui să o faci în 2 pași sau să-l scrii cu mâna lungă.
adăugat autor VulgarBinary, sursa
@ AaronLS Vad unde am înțeles greșit întrebarea acum. Dar sunt încă confuză cu privire la un singur lucru. Nu este p.PersonHistories un ICollection ? Dacă da, de ce să nu faci doar . Unde (p.Ends> ...) FirstOrDefault() Nume pentru a obține valoarea dorită?
adăugat autor Steve Mallory, sursa
@AaronLS Da, toate destul de un pic de muncă! Sper că veți obține. Am scris câteva clase care implementează ExpressionVisitor, dar nu am putut să-mi dau seama cum se aplică ceea ce am făcut pentru problema dvs.
adăugat autor Steve Mallory, sursa
Acest lucru se poate datora naturii lui EF w.r.t. Linq la SQL. Am intrat într-o problemă similară în care am terminat de a repeta o interogare complexă din nou, din cauză că motorul nu a putut citi metadatele evidente (la om, cel puțin) pentru a emite interogarea corectă.
adăugat autor bluevector, sursa

În loc de a utiliza expresii, de ex.

Expression> IsCurrent = (p)
    => p.Ends > DateTime.Now && p.Starts <= DateTime.Now;

var query = db.PersonHistories.Where(IsCurrent);

Puteți defini metode de extensie, cum ar fi:

public static IsCurrent Func<
    IQueryable, DateTime, IQueryable
    >()
{
    return (IQueryable query, DateTime referenceDate) =>
        query.Where(p.Ends > referenceDate && p.Starts <= referenceDate);
}

The use it like this:

var query = IsCurrent();

var results = query(context.PeoplesHistory, referenceDate);

Sau:

var results = query(previousResults, referenceDate);

Sau alternativ:

public static IsCurrent Func, IQueryable>(
    DateTime referenceDate)
{
    return (IQueryable query) =>
        query.Where(p.Ends > referenceDate && p.Starts <= referenceDate);
}

var query = IsCurrent(refernceDate);

var results = query(context.PeoplesHistory);

În acest fel nu aveți nevoie de un cadru pentru construirea expresiilor.

1
adăugat