12

I have written a Linq to CRM query using CRM 2011 RC (v5) LINQ-to-CRM provider. I have a locally declared List<T> which I want to join to a CRM entity and I want the query to be executed on the CRM Server. An example might help:

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());
}

The code above compiles and executes, however, the filtering of the records is being performed locally after retrieving the entire CRM entity recordset. Obviously when the CRM entity contains thousands of rows the query will perform poorly or even timeout.

I have read about IQueryable and IEnumerable and tried converting the List using the AsQueryable() extension method, which had no effect. I need my above Linq query to run SQL like this:

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

Or using a temporary table if wanted to join on multiple fields. How can I accomplish this?

Matt
  • 4,656
  • 1
  • 22
  • 32
Iftekhar
  • 269
  • 2
  • 10

3 Answers3

13

After a lot of head banging and research I have resolved the issue by using Predicate Builder and LINQKit. I need to create an Or based predicate using the keys in my local List<T> then pass the predicate to the Where LINQ extension method. Importantly, I need to call the AsExpandable extension method exposed by LINQKit. So my code would look like this:

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);
}

The above code will run a SQL Statement on the CRM Server like this:

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

This means I can create a dynamic where clause at runtime and know that my query will run the filtering logic on the CRM SQL Server. Hope this helps somebody else.

Iftekhar
  • 269
  • 2
  • 10
1

Instead of playing with predicates you could also simply use the join expression for filtering.

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

Cheers, Lukasz

Lukasz
  • 11
  • 1
  • 1
    I wanted the query to run on the server. With the join expression the filtering is done locally on the client. – Iftekhar May 10 '11 at 08:39
0

Edit: Try that one:

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());
}

Edit: if you query provider don't support Contains, build a Where condition with multiple OR, you can use predicate builder to make that easy

Guillaume86
  • 14,341
  • 4
  • 53
  • 53
  • I am already using the Linq-to-Crm query provider - Can I use more than one provider in the same query? – Iftekhar Feb 09 '11 at 15:16
  • Ok after looking closer to your query, I think you should do it the other way around (no join but a contains) – Guillaume86 Feb 09 '11 at 15:41
  • I want my query to be translated to SQL and the filtering to run on the SQL Server. The Contains extension method simply filters locally. – Iftekhar Feb 09 '11 at 16:20
  • Contains can be translated to an IN statement in SQL, and you use local objects (ListOfAccounts), it's the best you can get IMHO – Guillaume86 Feb 09 '11 at 16:22
  • example with LinqPad: var ids = new []{ 1, 2, 3}; var query = Users.Where(u => ids.Contains(u.Id)); translate to: SELECT [t0].[Id], [t0].[Email], [t0].[Password] FROM [User] AS [t0] WHERE [t0].[Id] IN (@p0, @p1, @p2) – Guillaume86 Feb 09 '11 at 16:27
  • I have tried that and I get a System.NotSupportedException with message: "Invalid 'where' condition. An entity member is invoking an invalid property or method." – Iftekhar Feb 09 '11 at 17:01
  • It means the Query Provider you use didn't implement the Contains operator, you should contact the devs and ask for it ;). Another way around would be to add where conditions for each value in local list, I'll edit to show – Guillaume86 Feb 09 '11 at 17:03
  • How can you dynamically add a where condition for each item in the list? – Iftekhar Feb 24 '11 at 14:27
  • foreach(var item in itemList) { query = query.Where(a => a.bla == item); } but that way you add conditions with "AND", to join with "OR" you can build a predicate Func by accumulation: Func predicate = (a) => true; foreach(var item in items) { predicate = (a) => predicate(a) || a.bla == item; } query = query.Where(predicate); – Guillaume86 Feb 24 '11 at 17:13
  • I wish to join with the OR predicate. I have tried your suggestion, but I get a StackOverflow Exception (irony!!!) – Iftekhar Feb 25 '11 at 09:54
  • Ok I didn't actually test my code but I can see how building a lambda expression that way can cause that exception, you need to copy the predicate inside the loop and reference the copy when you combine to create the new one. Try with PredicateBuilder it will be easier: http://www.albahari.com/nutshell/predicatebuilder.aspx – Guillaume86 Feb 25 '11 at 12:52
  • I have already tried using PredicateBuilder, but I get a NotSupportedException Invalid 'where' condition. An entity member is invoking an invalid property or method. Back to Square 1 :) – Iftekhar Feb 25 '11 at 13:23
  • I have managed to get it working. I need to call the AsExpandable method from LINQKit I will post the final code later. Thanks for your Help – Iftekhar Feb 25 '11 at 15:31