Implementieren Sie den IQueryable-Wrapper, um Ergebnisobjekte zu übersetzen

Update 22.08.2013:

Nachdem ich mir die 'Building an IQueryable provider series' angesehen habe (danke für den Link!), Bin ich noch ein bisschen weiter gegangen. Ich habe den Code entsprechend aktualisiert. Es ist immer noch nicht voll funktionsfähig. Wenn ich das Tutorial richtig verstehe, wird dasGetEnumerator wird aufgerufen, wenn mehrere Elemente angefordert werden (z. B. durch aToList() Aufruf der abfragbaren oder einer beliebigen Aggregationsfunktion). Also das ganzeGetEnumerator Die Implementierung des Wrappers muss über einen Aufruf von erfolgenExecute auf den Provider und übergeben Sie den Ausdruck des Abfragbaren. Im anderen Fall, wenn nur ein einzelnes Element angefordert wird,Execute wird direkt aufgerufen. Der Ausdruck des Abfragbaren gibt auch an, ob es sich um ein einzelnes oder mehrere Elemente handelt. Ist das richtig?

Leider erhalte ich jetzt eine InvalidOperationException-Meldung'Sequenz enthält mehr als ein Element' beim anrufenExecute auf dem Quellabfrageanbieter. Was bedeutet das? Ich übergebe den Ausdruck nur ohne Übersetzung, da es sich um dieselben Typen handelt, wie oben erwähnt. Das Übersetzungsstück mitIEnumerable im Code ist wahrscheinlich unvollständig, aber bis jetzt komme ich noch nicht einmal an diesen Punkt.

Ich versuche, einen einfachen IQueryable-Wrapper mit einem einzelnen zugrunde liegenden IQueryable als Datenquelle zu implementieren, der eine Übersetzungsfunktion für jedes Ergebnisobjekt aufruft.

Ich dachte, das wäre relativ trivial, da der Wrapper nur übersetzen muss. Allerdings konnte ich meine Implementierung nicht zum Laufen bringen.

Unten sehen Sie, was ich bisher bekommen habe. Bei einigen Anfragen klappt es aber ich bekomme eineStackOverflowException Irgendwann InvalidOperationException.Ich vermute, dass dies aufgrund der zyklischen Zuordnung zwischen meinem abfragbaren und meinem Abfrageanbieter geschieht. Aber ich verstehe nicht, wie ich das richtig umsetzen soll.

Hier meine Fragen und Gedanken dazu:

1. Warum hat der IQueryable einen Provider, der wiederum einen IQueryable zurückgibt? Verlangt das nicht eine endlose Rekursion?

2. Warum reicht es nicht aus, IEnumerator zu implementieren? Warum verwendet FirstOrDefault beispielsweise nicht den Enumerator, um das Element abzurufen? Beim Debuggen wurde die Anwendung GetEnumerator () nicht von FirstOrDefault () für meine Abfrage aufgerufen.

3. Wo ist der richtige Punkt, um die Übersetzungsfunktion aufzurufen, da der Enumerator nicht in jedem Fall verwandt wird? Die Execute-Methoden des QueryProviders schienen der richtige Ort zu sein. Aber brauche ich in manchen Fällen noch den Übersetzungsaufruf im Enumerator? Aktualisieren: Mir ist klar geworden, dass ich meine eigenen zur Verfügung stellen mussIEnumerable Implementierung bietet dieTranslatingEnumerator und gib diese Aufzählung von mir zurückExecute Methode. Um den Enumerator zu bekommenGetEnumerator AnrufeExecute (siehe unten). Der LINQ-Code, der den Enumerator anfordert, scheint sicherzustellen, dass der Ausdruck tatsächlich ein zurückgibtIEnumerable.

Einige Randnotizen zum Code:

Der Übersetzungsquellentyp wird benanntTDatabaseEntitywird der Übersetzungszieltyp benanntTBusinessEntity.

Ich stelle im Wesentlichen ein IQueryable bereit, das die Ergebnisobjekte, die aus einem zugrunde liegenden IQueryable abgerufen wurden, in das TBusinessEntity übersetztArt Objekte.

Mir ist bewusst, dass der Ausdruck auch übersetzt werden muss. Ich habe dies jedoch beiseite gelegt, da ich in meiner eigentlichen Anwendung dieselben Typen für TBusinessEntity und TDatabaseEntity verwende, sodass der Ausdruck direkt weitergeleitet werden kann.

Die Ergebnisobjekte müssen jedoch noch in andere Instanzen übersetzt werden, obwohl sie vom gleichen Typ sind.Aktualisieren: Meine Übersetzungsebene funktioniert bereits in meiner Anwendung und kümmert sich auch um verwandte Entitäten. Es ist nur das "Implementieren eines IQueryable-Wrappers", woran ich festhalte.

Ich fürchte, die gesamte Implementierung ist falsch - Die TODOs im Code sind nur meine eigenen Notizen.

Hintergrund: Ich implementiere meine eigene Trennung von Entitäten, die von DbContext empfangen wurden, in meiner Datenzugriffsebene, um zu verhindern, dass meine Geschäftsschicht mit den tatsächlichen Entitäten in Kontakt kommt - aufgrund einiger Fehler mit EF und anderer Anforderungen, die ich nicht direkt verwenden kann abgelöste Einheiten.

Danke für Ihre Hilfe!

IQueryable-Implementierung
internal class TranslatingQueryable<TDatabaseEntity, TBusinessEntity> : IQueryable<TBusinessEntity>
{
    private readonly IQueryProvider _provider;
    private readonly IQueryable<TDatabaseEntity> _source;

    internal TranslatingQueryable(TranslatingQueryProvider provider, IQueryable<TDatabaseEntity> source)
    {
        Guard.ThrowIfArgumentNull(provider, "provider");
        Guard.ThrowIfArgumentNull(source, "source");

        _provider = provider;
        _source = source;
    }

    internal TranslatingQueryable(Func<object, object> translateFunc, IQueryable<TDatabaseEntity> databaseQueryable)
        : this(new TranslatingQueryProvider(translateFunc, databaseQueryable.Provider), databaseQueryable)
    {
    }

    public IEnumerator<TBusinessEntity> GetEnumerator()
    {
        return ((IEnumerable<TBusinessEntity>)Provider.Execute(Expression)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)Provider.Execute(Expression)).GetEnumerator();
    }

    public Expression Expression
    {
        get
        {
            return _source.Expression;
        }
    }

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

    public IQueryProvider Provider
    {
        get
        {
            return _provider;
        }
    }
}
IQueryProvider-Implementierung
public class TranslatingQueryProvider : IQueryProvider
{
    private readonly Func<object, object> _translateFunc;
    private readonly IQueryProvider _databaseQueryProvider;

    public TranslatingQueryProvider(Func<object, object> translateFunc, IQueryProvider databaseQueryProvider)
    {
        _translateFunc = translateFunc;
        _databaseQueryProvider = databaseQueryProvider;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression);

        return new TranslatingQueryable<object, object>(this, databaseQueryable);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression);

        return new TranslatingQueryable<object, TElement>(this, databaseQueryable);
    }

    public object Execute(Expression expression)
    {
        return Execute<object>(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        // TODO This call throws an InvalidOperationException if an enumeration is requested
        var databaseResult = _databaseQueryProvider.Execute<TResult>(expression);

        var databaseEnumerable = databaseResult as IEnumerable;
        if (databaseEnumerable != null)
        {
            if (typeof(TResult).IsAssignableFrom(typeof(IEnumerable)))
            {
                throw new InvalidOperationException();
            }

            return (TResult)(object)new TranslatingEnumerable(databaseEnumerable, _translateFunc);
        }
        else
        {
            return (TResult)_translateFunc(databaseResult);
        }
    }

    private class TranslatingEnumerable : IEnumerable
    {
        private readonly TranslatingEnumerator _enumerator;

        public TranslatingEnumerable(IEnumerable databaseEnumerable, Func<object, object> translateFunc)
        {
            _enumerator = new TranslatingEnumerator(translateFunc, databaseEnumerable.GetEnumerator());
        }

        public IEnumerator GetEnumerator()
        {
            return _enumerator;
        }
    }
}
IEnumerator-Implementierung
internal class TranslatingEnumerator : IEnumerator
{
    private readonly Func<object, object> _translateFunc;
    private readonly IEnumerator _databaseEnumerator;

    internal TranslatingEnumerator(Func<object, object> translateFunc, IEnumerator databaseEnumerator)
    {
        _translateFunc = translateFunc;
        _databaseEnumerator = databaseEnumerator;
    }

    public bool MoveNext()
    {
        return _databaseEnumerator.MoveNext();
    }

    public void Reset()
    {
        _databaseEnumerator.Reset();
    }

    public object Current
    {
        get
        {
            return _translateFunc(_databaseEnumerator.Current);
        }
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }
}

Antworten auf die Frage(2)

Ihre Antwort auf die Frage