Применить «Список» IQueryables к цели? - PullRequest
0 голосов
/ 25 апреля 2018

У меня есть идея создать «список» IQueryables, выполняющих различные виды операций.

Итак, в основном:

var query1 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Name == "Ronald");
var query2 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Age == 43);
var query3 = Enumerable.Empty<Person>().AsQueryable().Select(e => e.EyeColor);

var listOfQueries = new List<IQueryable<Person>
{
    query1,
    query2,
    query3
};

Теперь у меня также есть полный набор DbSet "Персоны ", и я хотел бы" применить "все мои запросы к этому DbSet.Как бы я это сделал?Возможно ли это?

Обновленный пример:

        var personQueryFactory = new PersonQueryFactory();
        var personQueryByFirstname = personQueryFactory.CreateQueryByFirstname("Ronald");           //Query by Firstname.
        var personQueryByAge = personQueryFactory.CreateQueryByAge(42);                             //Query by age.
        var personQueryByHasChildWithAgeOver = personQueryFactory.CreateQueryByChildAgeOver(25);    //Query using a "join" to the child-relationship.
        var personQuerySkip = personQueryFactory.Take(5);                                           //Only get the 5 first matching the queries.

        var personQuery = personQueryFactory.AggregateQueries                                       //Aggragate all the queries into one single query.
        (
            personQueryByFirstname,
            personQueryByAge,
            personQueryByHasChildWithAgeOver,
            personQuerySkip
        );

        var personSurnames = personsService.Query(personQuery, e => new { Surname = e.Surname });          //Get only the surname of the first 5 persons with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDomainObjects = personsService.Query<DomainPerson>(personQuery);          //Get the first 5 persons as a domain-object (mapping behind the "scenes") with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDaos = personsService.Query(personQuery);          //Get the first 5 persons as a DAO-objects/entityframework-entities with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.

Причиной для этого будет создание более «унифицированного» способа создания и повторного использования предопределенных запросов, а затемчтобы иметь возможность выполнять их с DbSet и возвращать результат как объект-домен, а не как «объект-каркас-модель / объект»

Ответы [ 2 ]

0 голосов
/ 25 апреля 2018

Одна из проблем заключается в том, что коллекция IQueryable действительна только до тех пор, пока ваш DbSet действителен. Как только ваш DbContext будет удален, ваша тщательно заполненная коллекция станет бесполезной.

Так что вам нужно подумать о другом методе восстановления запроса, чем тот, который использует DbSet<Person>

Хотя на первый взгляд они кажутся одинаковыми, между IEnumerable и IQueryable есть разница. В Enumerable есть все, чтобы перечислять полученную последовательность.

С другой стороны, Queryable содержит Expression и Provider. Провайдер знает, где можно получить данные. Обычно это база данных, но это также может быть CSV-файл или другие элементы, из которых можно получить последовательности. Поставщик должен интерпретировать выражение и перевести его в формат, понятный базе данных, обычно SQL.

При объединении операторов linq в один большой оператор linq доступ к базе данных не осуществляется. Изменено только выражение.

Как только вы вызываете GetEnumerator () и MoveNext () (обычно с помощью ForEach, ToList () или аналогичного), выражение отправляется поставщику, который преобразует его в формат запроса, который база данных понимает, и выполняет запрос. Результатом запроса является последовательность Enumerable, поэтому вызываются Getenumerator () и MoveNext () результата запроса провайдера.

Поскольку ваш IQueryable содержит этого провайдера, вы не можете больше перечислять его после удаления провайдера.

При использовании платформы сущностей DbSet содержит Провайдера. В провайдере находится информация базы данных, хранящаяся в DbContext. После удаления DbContext вы больше не сможете использовать IQueryable:

IQueryable<Person> query = null;
using (var dbContext = new MyDbcontext())
{
     query = dbContext.Persons.Where(person => person.Age > 20);
}
foreach (var person in query)
{
      // expect exception: the DbContext is already Disposed
}

Таким образом, вы не можете поместить Провайдера в свою коллекцию или возможные запросы. Тем не менее, вы могли вспомнить выражение. Единственное, что вам требуется от вашего выражения, это то, что оно возвращает человека. Вам также нужна функция, которая использует это выражение и QueryaProvider for Persons для преобразования его в IQueryable.

Давайте создадим для этого универсальную функцию, чтобы ее можно было использовать для любого типа, а не только для Persons:

static IQueryable<TSource> ToQueryable<TSource>(this IQueryProvider provider, 
    Expression expression)
{
     return provider.CreateQuery(expression);
}

// хорошо, давайте добавим также следующее:

static IQueryable<Tsource> ToQueryable<TSource>(this DbContext dbContext, 
     Expression expression)
{
     return dbContext.Set<TSource>.Provider.ToQueryable<TSource>(expression);
}

Для получения справки о функциях расширения см. Демистифицированные функции расширения

Теперь только после того, как вы создадите свою коллекцию выражений. Для быстрого поиска сделайте словарь:

enum PersonQuery
{
    ByFirstname,
    ByAge,
    ByHasChildWithAgeOver,
    Skip,
}

public IReadOnlyDictionary<PersonQuery, Expression> CreateExpressions()
{
    Dictionary<PersonQuery, Expression> dict = new Dictionary<PersonQuery, Expression>();
    using (var dbContext = new MyDbContext())
    {
         IQueryable<Person> queryByFirstName = dbContext.Persons
             .Where(...);
         dict.Add(PersonQuery.ByfirstName, queryByFirstName.Expression);
         ... // etc for the other queries
    }
    return dict.
}

Использование:

IReadOnlyCollection<Person> PerformQuery(PersonQuery queryId)
{
     using (var dbContext = new MyDbContext())
     {
          // get the Expression from the dictionary:
          var expression = this.QueryDictionary[queryId];
          // translate to IQueryable:
          var query = dbContext.ToQueryable<Person>(expression);
          // perform the query:
          return query.ToList();

          // because all items are fetched by now, the DbContext may be Disposed
     }
}
0 голосов
/ 25 апреля 2018

Это возможно, но вам нужно немного изменить свой подход.


Хранение фильтров

var query1 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Name == "Ronald");
var query2 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Age == 43);
var query3 = Enumerable.Empty<Person>().AsQueryable().Select(e => e.EyeColor);

Читая ваше намерение, вы на самом деле не хотите обрабатывать IQueryable объекты, а скорее параметр, который вы предоставляете методу Where.

Редактировать: я пропустил, что третий был Select(), а не Where(). Я поправил остальную часть ответа, как будто это также был Where(). См. Комментарии ниже для моего ответа о том, почему вы не можете легко смешать Select() с Where().

Func<Person,bool> filter1  = (e => e.Name == "Ronald");
Func<Person,bool> filter2  = (e => e.Age == 43);
Func<Person,bool> filter3  = (e => e.EyeColor == "Blue");

Здесь хранится та же информация (критерии фильтра), но он не оборачивает каждый фильтр в IQueryable своего собственного.

Краткое объяснение

Обратите внимание на обозначение Func<A,B>. В этом случае A - это тип ввода (Person), а B - это тип вывода (bool).

Это может быть расширено. Func<string,Person,bool> имеет два входных параметра (string, Person) и один выходной параметр (bool). Пример использования:

Func<string, Person, bool> filter = (inputString, inputPerson) => inputString == "TEST" && inputPerson.Age > 35;

Всегда есть один выходной параметр (последний тип). Любой другой упомянутый тип является входным параметром .


Размещение фильтров в списке

Интуитивно понятно, поскольку Func<Person,bool> представляет один фильтр; Вы можете представить список фильтров, используя List<Func<Person,bool>>.

Вложенные универсальные типы становятся немного сложными для чтения, но они работают так же, как и любые другие List<T>.

List<Func<Person,bool>> listOfFilters = new List<Func<Person,bool>>()
{
    (e => e.Name == "Ronald"),
    (e => e.Age == 43),
    (e => e.EyeColor == "Blue")
};

Выполнение фильтров

Тебе повезло. Поскольку вы хотите применить все фильтры (логическое И), вы можете сделать это очень просто, сложив их:

var myFilteredData = myContext.Set<Person>()
                                 .Where(filter1)
                                 .Where(filter2)
                                 .Where(filter3)
                                 .ToList();

Или, если вы используете List<Func<Person,bool>>:

var myFilteredData = myContext.Set<Person>().AsQueryable();

foreach(var filter in listOfFilters)
{
    myFilteredData = myFilteredData.Where(filter);
}

Бахромы

Однако, если вы пытаетесь найти все элементы, которые соответствуют одному или нескольким фильтрам (логическое ИЛИ), это становится немного сложнее.

Полный ответ довольно сложен. . Вы можете проверить это здесь.

Однако, если у вас установлены фильтры в известных переменных, есть более простой метод :

Func<Person, bool> filterCombined = 
              e => filter1(e) || filter2(e) || filter3(e);

var myFilteredData = myContext.Set<Person>()
                                 .Where(filterCombined)
                                 .ToList();
...