narzedzia

Ten o implementacji IQueryable

Pierwszym krokiem w implementacji naszego providera LINQ to Allegro będzie zaimplementowanie interfejsu IQueryable<T>. Dlaczego tylko jego? W końcu sam nie posiada żadnych metod. Wystarczy, ponieważ on sam zawiera w sobie pozostałe interfejsy wymagane do stworzenia providera LINQ i zawierające wymagane metody:

public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
{
}

IQueryable<T>

Po stworzeniu klasy, która ma go używać, dostajemy od VS informacje, jakie metody musimy zaimplementować:

IQueryable

Dodajmy najpierw trzy właściwości:

public Expression Expression { get; private set; }
public Type ElementType => typeof(T);
public IQueryProvider Provider { get; private set; }

Expression i Provider będziemy inicjalizować w konstruktorze, a ElementType to po prostu typeof(T). Do dodania zostaje jeszcze GetEnumerator x2 – zwykła i generyczna wersja:

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

Taka implementacja mówi nam, że po wywołaniu metody GetEnumerator zapytanie, które reprezentuje obiekt naszej klasy, zostanie wywołane na serwerze. Do realizacji tego zadania zostanie użyta metoda Execute, a to co zwróci – zostanie wyliczone.

W kolejnym kroku przedstawmy dwa konstruktory. Jeden bez parametrów, który będzie wywoływał klient, żeby stworzyć źródło danych i drugi, wywoływany przez metodę CreateQuery<T> z klasy implementującej interfejs IQueryProvider.

public AllegroQueryable()
{
    Provider = new AllegroQueryProvider();
    Expression = Expression.Constant(this);
}
public AllegroQueryable(AllegroQueryProvider provider, Expression expression)
{
    if (provider == null)
    {
        throw new ArgumentNullException("provider");
    }

    if (expression == null)
    {
        throw new ArgumentNullException("expression");
    }

    if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
    {
        throw new ArgumentOutOfRangeException("expression");
    }

    Provider = provider;
    Expression = expression;
}

Taka implementacja IQueryable<T> jest uniwersalna i można jej użyć do własnego providera LINQ.

Niby wszystko OK, ale…

IQueryProvider

W naszym kodzie nie mamy jeszcze klasy AllegroQueryProvider. Stwórzmy ją i pozwólmy jej implementować interfejs IQueryProvider, który wymaga od nas dwóch metod w dwóch wersjach – generycznej i niegenerycznej:

IQueryProvider
Metody CreateQuery są odpowiedzialne za stworzenie zapytania, które później wyślemy do naszego źródła danych przy pomocy Execute.

Jeśli przejrzymy MSDN’owski Walkthrough, zobaczymy implementację metod CreateQuery używającej refleksji:

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

Korzysta ona z części klasy TypeSystem, wyciętej z DLINQ, którą w całości możemy zobaczyć tutaj. Po co aż tyle? Nie wiem. Ja skróciłem tą implementację do wywołania metody generycznej z nazwą klasy, która zawiera pola zwracane przez serwer Allegro:

public IQueryable CreateQuery(Expression expression)
{
    return CreateQuery<AllegroItem>(expression);
}

Szczerze mówiąc, nie wiem jeszcze, czy nie wystarczyłoby nawet wpisać return null. Być może mylę się jeszcze bardziej i będzie trzeba jednak użyć innej implementacji. Mam nadzieję, że wyjdzie w praniu. Metody Execute są zaimplementowane podobnie do tych z Microsoftowego poradnika.

Na koniec dodajmy jeszcze tylko klasę AllegroQueryContext ze statyczną metodą Execute

public static object Execute(Expression expression)
{
    return new List<AllegroItem>
    {
        new AllegroItem {Title = "C# cośtam", ItemUri = new Uri("http://www.c1.com/")},
        new AllegroItem {Title = "C#", ItemUri = new Uri("http://www.c2.com/")}
    };
}

Zwracam nową listę tylko w celach testowych. W powyższej metodzie dostajemy ExpressionTree, które będziemy musieli przejrzeć i na jego podstawie skonstruować odpowiednie zapytanie. To, co tutaj zostanie zwrócone, zobaczymy podczas enumeracji naszego zapytania. Sprawdźmy, czy nasz program trafia do metody Execute. W klasie AllegroHelper umieśćmy następujący kod:

public static AllegroQueryable<AllegroItem> AllegroItems()
{
    return new AllegroQueryable<AllegroItem>();
}

A w funkcji Main:

var allegroItems = AllegroHelper.AllegroItems().Select(x=>x);
foreach (var allegroItem in allegroItems)
{
    Console.WriteLine(allegroItem.ToString());
}
Console.Read();

I kiedy na konsoli widzę dwa sztucznie dodane elementy, to wiem, że jest dobrze…

Cały kod oczywiście można znaleźć na moim GitHubie.

Kolejny post, z implementacją Execute, znajdziesz tutaj.

Jedno przemyślenie nt. „Ten o implementacji IQueryable

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *