2013-10-11 2 views
2

У меня есть строка:соответствие строки элементов идентификаторов в LINQ

strCheckedCategories = "2;" 

EntityList, представляющий список SharePoint, с идентификаторами элементов от 1 до 21:

EntityList<VendorSearchesItem> vendorSearches = 
    dataContext.GetList<VendorSearchesItem>("Vendor Searches"); 

запроса LINQ, возвращающих полей из два списка SharePoint, которые присоединились к списку «Vendor поисков»:

var vendorSearchesQuery = (from s in vendorSearches 
          orderby s.VendorID.Title 
          select new 
          { 
           Vendor = s.VendorID.Title, 
           Website = s.VendorID.VendorWebsite, 
           VendorID = s.VendorID.Id, 
           SearchType = s.SearchTypeID.Title, 
           SearchTypeId = s.SearchTypeID.Id 
          }); 

и другому запросу LINQ возвращающего только элемент ев, где идентификатор элемента в списке:

var q2 = from m2 in vendorSearchesQuery 
     where strCheckedCategories.Contains(m2.SearchTypeId.ToString()) 
     select m2 

Проблема заключается в том, что, в дополнение к возвращенной деталь с ID 2 (желаемый результат) запрос также возвращает элементы с ID 12, 20 и 21. Как я могу это исправить?

+1

if 'strCheckedCategories = 2', как бы' 2' содержать '12',' 20' и '21'? –

+0

@KingKing это я думаю, наоборот, 12 20 21 содержит 2 –

+0

Отредактированный вопрос, чтобы выправить это, спасибо. – LFurness

ответ

0

попробовать:

strCheckedCategories.Split(new []{';'}).Any(x => x == m2.SearchTypeId.ToString()) 

Содержит будет делать подстроку. А «20» имеет подстроку «2».

+0

Это не сможет перевести запрос в CAML, который может быть выполнен в базе данных. Поставщик запроса поймет, что он не может оценить выражение и вместо этого вытащит весь набор результатов и выполнит фильтрацию внутри приложения. Если размер списка невелик, это может стать серьезной проблемой. Это также может смущать будущих читателей, которые могут не понимать, что поставщик запросов не может его перевести. Если вы хотите, чтобы фильтрация была на стороне приложения, вы должны хотя бы использовать 'AsEnumerable', чтобы дать понять читателю, что это происходит. – Servy

0
var q2 = from m2 in vendorSearchesQuery 
     where strCheckedCategories.Split(';').Contains(m2.SearchTypeId.ToString()) 
     select m2 
+0

Я не уверен, что оптимизатор достаточно умен, чтобы выполнять только операцию «Разделить», а не для каждого элемента в 'vendorSearchesQuery' ... –

+1

@TrevorElliott Ну, во-первых, я могу заверить вас, что поставщик запросов не сможет правильно перевести этот запрос в тот, который может быть запущен в конце базы данных, что, вероятно, является проблемой, так как это должно быть цели здесь. Затем, поскольку это нужно выполнить на стороне клиента, используя LINQ для объектов, вы правы, что он будет разделять строку на каждой итерации, а не один раз, а затем повторно использовать ее. Итак, вы правы, но это действительно наименьшая проблема. – Servy

1

Вы, вероятно, следует использовать Regex, но если вы хотите более простое решение, то я хотел бы избежать строки поиска и разделить эти строки на массив:

string strCheckedCategories = "2;4;5;7;12;16;17;19;20;21;"; 
string[] split = strCheckedCategories.Split(';'); 

Это создаст пустую запись в массив для конечного разделителя с запятой. Я хотел бы проверить, что и удалить его, если это проблема:

strCheckedCategories.TrimEnd(';'); 

Наконец, теперь вы можете изменить where положение:

where split.Contains(m2.SearchTypeId.ToString()) 

Если у вас есть очень большой список это, вероятно, стоит сравнить целые вместо строк путем разбора strCheckedCategories в список целых чисел вместо:

int[] split = strCheckedCategories.Split(';').Select(x => Convert.ToInt32(x)).ToArray(); 

Затем вы можете сделать более быстрое выражение равенства:

where split.Contains(m2.SearchTypeId) 
0
var q2 = from m2 in vendorSearchesQuery 
     where strCheckedCategories.Contains(";" + m2.SearchTypeId + ";") 
     select m2 

И ваш strCheckedCategories всегда должен заканчиваться ; и начать с ;, например ;2;, ;2;3; ...

ПРИМЕЧАНИЕ: Этот трюк работает только тогда, когда ваш SearchTypeId должны всегда не содержат ;. Я думаю, вы должны использовать другой вид разделителя, например \n или просто сохраните свои проверенные категории в списке или в каком-то массиве. Это более стандартный способ сделать.

+1

Не соответствует ли, если SearchTypeId был «2», а strCheckedCategories содержит идентификатор «22»? Это все еще подстрока. –

+0

@TrevorElliott u're right, мы должны инкапсулировать все идентификаторы на ';' –

4

Итак, принципиально, что вы хотите сделать здесь, есть предложение IN, в котором вы указываете кучу значений для поля, и вы хотите, чтобы строки, значения которых для этого столбца находились в этом наборе.

У CAML действительно есть предложение IN, которое вы можете использовать, но, к сожалению, LINQ to Sharepoint не предоставляет никаких средств для создания предложения IN; поставщик просто не поддерживается.

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

Поскольку, как я уже сказал, вы не можете заставить LINQ to SharePoint использовать IN, один вариант просто заключался бы в том, чтобы не использовать LINQ, создавать CAML вручную и выполнять его с использованием стандартной объектной модели сервера. Но это не весело.

Что мы может действительно есть серия проверок ИЛИ. Мы увидим, является ли это значение столбца первым значением, или вторым, или третьим и т. Д. Для всех значений в вашем наборе. Это фактически идентично предложению IN, это намного больше подробностей.

Теперь это приводит нас к проблеме того, как OR вместе неизвестное количество сравнений. Если бы это было ANDs, это было бы легко, мы бы просто вызвали Where внутри цикла, и это было бы и те предложения N.

Вместо этого нам понадобятся выражения. Мы можем вручную построить дерево выражений самим из динамического числа предложений OR, а затем поставщик запросов сможет разбирать его просто отлично.

Наш новый метод, WhereIn, который будет фильтровать запрос ко всем элементам, где данное значение свойства находится в наборе значений, необходимо будет принять запрос, селектор свойств того, какое свойство мы используем, и набор значений того же типа для сравнения с ним. После того, как у нас есть, просто создать выражение сравнения доступа к свойствам вместе с каждым значением ключа, а затем ORing всех этих выражений.

public static IQueryable<TSource> WhereIn<TSource, TKey>(
    this IQueryable<TSource> query, 
    Expression<Func<TSource, TKey>> propertySelector, 
    IEnumerable<TKey> values) 
{ 
    var t = Expression.Parameter(typeof(TSource)); 
    Expression body = Expression.Constant(false); 
    var propertyName = ((MemberExpression)propertySelector.Body).Member.Name; 

    foreach (var value in values) 
    { 
     body = Expression.OrElse(body, 
      Expression.Equal(Expression.Property(t, propertyName), 
       Expression.Constant(value))); 
    } 

    return query.Where(Expression.Lambda<Func<TSource, bool>>(body, t)); 
} 

Теперь назвать это нам просто нужно запрос, свойство мы фильтрацию и сбор значений:

var q2 = vendorSearchesQuery.WhereIn(vendor => vendor.SearchTypeId 
    , strCheckedCategories.Split(';')); 

и вуаля.

Хотя я бы ожидать, что работать как есть, вы может нужно вызвать WhereInперед темSelect. Он может работать не совсем корректно с уже нанесенным на карту SearchTypeId.

+0

похоже, что вы пропустили ключевое слово 'this', чтобы определить расширение метода? –

+0

@KingKing Право. Я на самом деле намеренно не делал это методом расширения, когда писал, но потом забыл об этом, когда я его называл. Исправлена. – Servy

+0

Не могли бы вы добавить некоторые объяснения, почему это лучше, чем использование «Содержит»? Я не уверен, что 'Contains' работает в' LINQ-to-Sharepoint', но, как сказал OP, он компилирует OK, используя его также короче, чем определять 'WhereIn', я думаю, что у него должно быть что-то лучше сделать таким образом, спасибо. –