Программно изменить способ заполнения свойства в запросе LINQ to SQL - PullRequest
2 голосов
/ 03 марта 2010

Я пишу немного LINQ to SQL, который должен выполнить поиск информации о клиенте в нашей базе данных, отфильтровать результаты и разбить их на страницы. Фильтрация должна быть динамичной, поэтому я разбил настройку запроса на четыре этапа:

  1. Поиск видимых клиентов (в основном применяется грубый фильтр)
  2. Фильтр клиентов по критериям поиска
  3. Сортировка и просмотр клиентов
  4. Получить дополнительные данные клиента

Код выглядит следующим образом:

// Step 1
var visibleClientQuery = from x in xs
                         from y in ys
                         from z in yas
                         where
                             ...
                         select new
                         {
                             id = z.Client.Id,
                             a = z.Client.a,
                             b = z.Client.e.f.g.b
                         };

// Step 2
if (filterByA)
{
    visibleClientQuery = visibleClientQuery.Where(client => client.a > 10);
}
else
{
    visibleClientQuery = visibleClientQuery.Where(client => client.b.StartsWith("ABC"));
}

// Step 3
visibleClientQuery = visibleClientQuery.Distinct();

if (filterByA)
{
    visibleClientQuery = visibleClientQuery.OrderBy(client => client.a);
}
else
{
    visibleClientQuery = visibleClientQuery.OrderBy(client => client.b);
}

visibleClientQuery = visibleClientQuery.Skip(50).Take(30);

// Step 4
var fullQuery = from filterClientDetail in filteredClientQuery
                 join client in Clients on filterClientDetail.Id equals client.Id
                 ...;

LINQ to SQL отлично справляется с объединением этих элементов для создания эффективного запроса. Но я хочу уменьшить количество соединений, созданных в первом грубом запросе. Если я фильтрую и сортирую по A, нет необходимости для первого запроса заполнять b с помощью строки:

b = z.Client.e.f.g.b

Не заполнение b спасло бы все это дополнительное присоединение. Я попытался заменить строку на:

b = filterByA ? string.Empty : z.Client.e.f.g.b

Хотя это работает функционально, когда filterByA имеет значение true, избыточные объединения все еще присутствуют в запросе ... замедляя его. Мой обходной путь - ввести другой промежуточный запрос, который оборачивает запрос шага 1:

// Step 1
var visibleClientQuery = from x in xs
                         from y in ys
                         from z in yas
                         where
                             ...
                         select z.Client;

// Step 2
var filteredClientQuery = from client in visibleClientQuery
                          select new
                          {
                              id = client.Id,
                              a = client.a,
                              b = string.Empty
                          };

Тогда если нам нужно отфильтровать по B, это будет заменено на:

filteredClientQuery = from client in visibleClientQuery
                      select new
                      {
                          id = client.Id,
                          a = 0,
                          b = client.e.f.g.b
                      };

Пока запрос на замену возвращает анонимный класс с теми же свойствами, это работает, но кажется ненужным, тяжелым взломом и не позволяет легко смешивать и сопоставлять фильтры ... что если нам нужно фильтровать по А и Б? В реальном запросе есть еще несколько возможных фильтров.

Есть ли способ программно изменить способ заполнения отдельного свойства в анонимном классе, возвращаемом из запроса ... во многом таким же образом, как можно изменить предложение where? С помощью FilterClientQuery.Select (...) я могу поменять весь выбор, но не вижу способа работать с отдельным свойством.

Любая помощь приветствуется ... даже если она просто подтверждает, что решения не существует!

Ответы [ 4 ]

1 голос
/ 04 марта 2010

Поэтому, отвергнув использование строк и решив, что ручное построение выражения будет слишком сложным (спасибо Дэвиду Б за ваши усилия), я решил попробовать LinqKit . Это основано на каком-то классном коде, написанном Tomas Petricek . У меня было несколько неудачных попыток, но через 30 минут все заработало именно так, как я надеялся. Теперь у меня есть код примерно так:

// Step 1
var visibleZQuery = from x in xs.AsExpandable()
                         from y in ys
                         from z in yas
                         where
                             ...
                         select Z;

// Step 2
if (filterByA)
{
    visibleZQuery = visibleZQuery.Where(client => client.a > 10);
}

if (filterByB)
{
    visibleZQuery = visibleZQuery.Where(client => client.b.StartsWith("ABC"));
}

Expression<Func<Z, string>> aSelector = z => string.Empty;
Expression<Func<Z, string>> bSelector = z => string.Empty;

if (filterByA)
{
    aSelector = z => z.Client.a;
}

if (filterByB)
{
    bSelector = z => z.Client.e.f.g.b;
}

var filteredClientQuery = from z in visibleZQuery
                          select new 
                          { 
                              id = z.Client.Id, 
                              a = aSelector.Invoke(z), 
                              b = aSelector.Invoke(z)
                          }; 

// Step 3
filteredClientQuery = filteredClientQuery.Distinct();

if (filterByA)
{
    filteredClientQuery = filteredClientQuery.OrderBy(client => client.a);
}
else if (filterByB)
{
    filteredClientQuery = filteredClientQuery.OrderBy(client => client.b);
}

filteredClientQuery = filteredClientQuery.Skip(50).Take(30);

// Step 4
var fullQuery = from filterClientSummary in filteredClientQuery
                 join client in Clients on filterClientSummary.Id equals client.Id
                 ...;

В коде есть несколько изменений, но наиболее важным является то, что первый запрос использует AsExpandable () для представления обертки LinqKit, а оператор select на шаге 2 заполняется выражениями, которые определены вне конструкции основного запрос. Все безопасно для типов, и, кроме AsExpandable (), остальная часть кода работает так же, как и разработчик, привыкший к Linq to SQL.

Я подтвердил, что этот подход все еще создает эффективный SQL, который теперь присоединяется к таблицам, только если они действительно необходимы.

Спасибо Тиморесу и Дэвиду Б за ваш вклад!

1 голос
/ 03 марта 2010

Вы должны написать свой запрос к классу отображения как можно больше. Сдай контроль.

Редактировать: не позволяйте анонимным объявлениям типа ограничивать ваше мышление. Не стесняйтесь объявлять пользовательский тип для хранения пользовательского результата. В конце концов, анонимный тип просто позволяет компилятору создавать / называть тип для вас.

public class SubClient
{
   public int id {get;set;}
   public int? a {get;set;}
   public string b {get;set;}
}


// Step 1 
IQueryable<Client> visibleClientQuery =
    from x in xs 
    from y in ys 
    from z in yas 
    where 
    ... 
    select z.Client;

IQueryable<SubClient> subClientQuery = null;

// Step 2 
if (filterByA) 
{ 
    subClientQuery = visibleClientQuery.Select(c => new SubClient()
    {
        id = c.id,
        a = c.a
    }).Where(x => x.a > 10); 
} 
else 
{ 
    subClientQuery = visibleClientQuery.Select(c => new SubClient()
    {
      id = c.id,
      b = c.e.f.g.b
    })
    .Where(x => x.b.StartsWith("ABC")); 
}

// Step 3 
subClientQuery = subClientQuery.Distinct(); 

if (filterByA) 
{ 
    subClientQuery = subClientQuery.OrderBy(c => c.a); 
} 
else 
{ 
    subClientQuery = subClientQuery.OrderBy(c => c.b); 
} 

subClientQuery = subClientQuery.Skip(50).Take(30); 

Редактировать: Бонус в ответ на комментарий.

Если вы хотите Func<Client, string>, вы получите это так:

Func<Client, string> func = c => c.e.f.g.b;

Если вы хотите Expression, сделайте это следующим образом (об этом позаботится компилятор).

Expression<Func<Client, string>> expr = c => c.e.f.g.b;

У меня фактически была эта строка в моем коде, пока я не понял, что будет трудно использовать ее в вызове Where. Может быть, вы можете понять это.


Expression<Func<Client, SubClient>> selectExpr =
  GetSelectExpressionForFilterCriteria(filterByA, filterByB,
    filterByC, filterByD, filterByE);

Expression<Func<SubClient, bool>> filterExpr =
  GetFilterExpressionForFilterCriteria(filterByA, filterByB,
    filterByC, filterByD, filterByE);

subClientQuery = clientQuery.Select(selectExpr).Where(filterExpr);
1 голос
/ 04 марта 2010

как вы реализуете GetSelectExpressionForFilterCriteria?

Вот что я придумал для реализации После прочтения этих статей.

http://www.codewrecks.com/blog/index.php/2009/08/12/manipulate-expression-tree-in-dtogenerator/

http://msdn.microsoft.com/en-us/library/bb344570.aspx

Это полностью от руки и не проверено. Я бы даже не опубликовал его, если бы не было так легко проверить конструкцию Expression с помощью ToString ();

using System.Linq.Expressions;
using System.Reflection;


NewExpression newSubClient = Expression.New(typeof(SubClient));
Console.WriteLine(newSubClient.ToString());

List<MemberBinding> bindings = new List<MemberBinding>();
ParameterExpression paramExpr = Expression.Parameter(typeof(Client), "c");

MemberInfo idMember = typeof(SubClient).GetMember("Id")[0];
MemberBinding idBinding = Expression.Bind(
  idMember,
  Expression.Property(paramExpr, "id")
);
Console.WriteLine(idBinding.ToString());

//save it for later
bindings.Add(idBinding);

if (filterByA)
{
  MemberInfo aMember = typeof(SubClient).GetMember("a")[0];  
  MemberBinding aBinding = Expression.Bind(
    aMember,
    Expression.Property(paramExpr, "a")
  );
  Console.WriteLine(aBinding.ToString());

  //save it for later
  bindings.Add(aBinding);
}
if (filterByB)
{
  MemberInfo bMember = typeof(SubClient).GetMember("b")[0];
  MemberBinding bBinding = Expression.Bind(
    aMember,
    Expression.Property(
      Expression.Property(
        Expression.Property(
          Expression.Property(paramExpr, "e")
        , "f")
      , "g")
    , "b")
  );
  Console.WriteLine(bBinding.ToString());

  //save it for later
  bindings.Add(bBinding);
}

MemberInitExpression newWithInit = Expression.MemberInit
(
  newSubClient,
  bindings  //use any bindings that we created.
);
Console.WriteLine(newWithInit.ToString());

Expression<Func<Client, SubClient>> result =
  Expression.Lambda<Func<Client, SubClient>>
  (
    newWithInit,
    paramExpr
  );

Console.WriteLine(result);
return result;
0 голосов
/ 03 марта 2010

Если вы можете принять отсутствие безопасности типов, вы можете попробовать Dynamic Linq, как описано в Скотт Гатри , где строки используются для представления различных частей запроса.

Пример (приведенный в загрузке, на который указывает Скотт):

   var query =
        db.Customers.Where("City == @0 and Orders.Count >= @1", "London", 10).
        OrderBy("CompanyName").
        Select("New(CompanyName as Name, Phone)");

Использование строк в запросе - это шаг назад, но в особом случае динамического запроса проще создавать различные фрагменты (Where, Select) через строки, добавляя требуемый фильтр или выбранный столбец как вы анализируете то, что запросил пользователь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...