LINQ to Dynamics CRM Query фильтрует записи локально

Я написал запрос Linq to CRM с использованием поставщика LINQ-to-CRM CRM 2011 RC (v5). У меня есть объявленный локально Список ‹T›, который я хочу присоединить к объекту CRM, и я хочу, чтобы запрос выполнялся на сервере CRM. Пример может помочь:

MyObject myObject = new MyObject();
List<myAccount> myAccountsList = new List<myAccount>();

myAccountsList.Add(new myAccount() {AccountNumber = "123"};
myAccountsList.Add(new myAccount() {AccountNumber = "456"};

myObject.ListOfAccounts = myAccountsList;

var accountsQuery = from ax in myObject.ListOfAccounts
                    join a in orgContext.CreateQuery<customAccountEntity>() on ax.AccountNumber equals a.account_number
                    select a;

foreach(var item in accountsQuery)
{
    Console.WriteLine("Id of record retrieved: " + a.Id.ToString());
}

Приведенный выше код компилируется и выполняется, однако фильтрация записей выполняется локально после получения всего набора записей объекта CRM. Очевидно, что когда объект CRM содержит тысячи строк, запрос будет выполняться плохо или даже по истечении времени ожидания.

Я прочитал об IQueryable и IEnumerable и попытался преобразовать список с помощью метода расширения AsQueryable (), который не дал результата. Мне нужен мой вышеупомянутый запрос Linq для запуска SQL следующим образом:

SELECT a.*
FROM customAccountEntity AS a
WHERE a.account_number IN ('123', '456');

Или используйте временную таблицу, если хотите объединить несколько полей. Как я могу этого добиться?


person Iftekhar    schedule 09.02.2011    source источник


Ответы (3)


После долгих размышлений и исследований я решил проблему с помощью Predicate Builder и LINQKit. Мне нужно создать предикат на основе Or, используя ключи в моем локальном списке ‹T>, а затем передать предикат в метод расширения Where LINQ. Важно отметить, что мне нужно вызвать метод расширения AsExpandable, предоставляемый LINQKit. Итак, мой код будет выглядеть так:

var predicate = PredicateBuilder.False<customAccountEntity>();
// Loop through the local List creating an Or based predicate
foreach (var item in myAccountsList)
{
    string temp = item.AccountNumber;
    predicate = predicate.Or (x => x.customCrmEntityAttribute == temp);
}
// The variable predicate is of type Expression<Func<customAccountEntity, bool>>
var myLinqToCrmQuery =  from ax in myObject.ListOfAccounts
                        from cx in orgContext.CreateQuery<customAccountEntity>().AsExpandable().Where(predicate)
                        where ax.AccountNumber == cx.account_number
                        select cx;

foreach (resultItem in myLinqToCrmQuery)
{
    Console.WriteLine("Account Id: " + resultItem.Id);
}

Приведенный выше код будет запускать оператор SQL на сервере CRM следующим образом:

SELECT a.*
FROM customAccountEntity AS a
WHERE a.account_number = '123' OR a.account_number = '456'

Это означает, что я могу создать динамическое предложение where во время выполнения и знать, что мой запрос будет запускать логику фильтрации на CRM SQL Server. Надеюсь, это поможет кому-то другому.

person Iftekhar    schedule 01.03.2011

Вместо того, чтобы играть с предикатами, вы также можете просто использовать выражение соединения для фильтрации.

var myLinqToCrmQuery =  from ax in myObject.ListOfAccounts
                            join cx in orgContext.CreateQuery<customAccountEntity> on ax.AccountNumber equals cx.account_number                      
                            select cx;

Привет, Лукаш

person Lukasz    schedule 09.05.2011
comment
Я хотел, чтобы запрос выполнялся на сервере. С помощью выражения соединения фильтрация выполняется локально на клиенте. - person Iftekhar; 10.05.2011

Изменить: попробуйте это:

MyObject myObject = new MyObject();
List<myAccount> myAccountsList = new List<myAccount>();

myAccountsList.Add(new myAccount() {AccountNumber = "123"};
myAccountsList.Add(new myAccount() {AccountNumber = "456"};

myObject.ListOfAccounts = myAccountsList;

var accountNumbers = myObject.ListOfAccounts.Select(a => a.AccountNumber);

var accountsQuery = orgContext.CreateQuery<customAccountEntity>()
                              .Where(a => accountNumbers.Contains(a.account_number));

foreach(var item in accountsQuery)
{
    Console.WriteLine("Id of record retrieved: " + a.Id.ToString());
}

Изменить: если ваш поставщик запросов не поддерживает Contains, создайте условие Where с несколькими OR, вы можете использовать построитель предикатов, чтобы упростить эту задачу

person Guillaume86    schedule 09.02.2011
comment
Я уже использую поставщик запросов Linq-to-Crm. Могу ли я использовать более одного поставщика в одном запросе? - person Iftekhar; 09.02.2011
comment
Хорошо, посмотрев ближе к вашему запросу, я думаю, вам следует сделать это наоборот (без соединения, но содержит) - person Guillaume86; 09.02.2011
comment
Я хочу, чтобы мой запрос был переведен на SQL, а фильтрация выполнялась на SQL Server. Метод расширения Contains просто фильтрует локально. - person Iftekhar; 09.02.2011
comment
Contains можно преобразовать в оператор IN в SQL, и вы используете локальные объекты (ListOfAccounts), это лучшее, что вы можете получить IMHO - person Guillaume86; 09.02.2011
comment
пример с LinqPad: var ids = new [] {1, 2, 3}; var query = Users.Where (u = ›ids.Contains (u.Id)); перевести на: SELECT [t0]. [Id], [t0]. [Email], [t0]. [Password] FROM [User] AS [t0] WHERE [t0]. [Id] IN (@ p0, @ p1 , @ p2) - person Guillaume86; 09.02.2011
comment
Я пробовал это и получаю System.NotSupportedException с сообщением: Invalid 'where' condition. Член сущности вызывает недопустимое свойство или метод. - person Iftekhar; 09.02.2011
comment
Это означает, что используемый вами поставщик запросов не реализовал оператор Contains, вам следует связаться с разработчиками и попросить об этом;). Другой способ - добавить условия для каждого значения в локальном списке, я отредактирую, чтобы показать - person Guillaume86; 09.02.2011
comment
Как можно динамически добавлять условие where для каждого элемента в списке? - person Iftekhar; 24.02.2011
comment
foreach (var item в itemList) {query = query.Where (a = ›a.bla == item); } но таким образом вы добавляете условия с помощью AND, а для соединения с OR вы можете построить предикат Func ‹T, bool› путем накопления: Func ‹T, bool› predicate = (a) = ›true; foreach (var item in items) {predicate = (a) = ›predicate (a) || a.bla == item; } query = query.Where (предикат); - person Guillaume86; 24.02.2011
comment
Я хочу присоединиться к предикату OR. Я попробовал ваше предложение, но получаю исключение StackOverflow (ирония !!!) - person Iftekhar; 25.02.2011
comment
Хорошо, я на самом деле не тестировал свой код, но я вижу, как построение лямбда-выражения таким образом может вызвать это исключение, вам нужно скопировать предикат внутри цикла и ссылаться на копию при объединении для создания нового. Попробуйте с PredicateBuilder, это будет проще: albahari.com/nutshell/predicatebuilder.aspx - person Guillaume86; 25.02.2011
comment
Я уже пробовал использовать PredicateBuilder, но получаю NotSupportedException Invalid "where". Член сущности вызывает недопустимое свойство или метод. Вернуться на площадь 1 :) - person Iftekhar; 25.02.2011
comment
Мне удалось заставить его работать. Мне нужно вызвать метод AsExpandable из LINQKit. Я опубликую окончательный код позже. Спасибо за вашу помощь - person Iftekhar; 25.02.2011