问题
I'm using my OrganizationServiceContext implementation generated by the svcutil to retrieve entities from CRM:
context.new_productSet.First(p => p.new_name == "Product 1");
Is it possible to retrieve multiple entities with different attribute values at once - (smth like IN operator in SQL)?
Example: I would like to retrieve multiple products ("Product 1", "Product 2", ...) with a single call. The list of product names is dynamic, stored in an array called productNames.
回答1:
No, you can't. CRM LINQ provider only allows variables to appear on the left side of expressions, while the right side must contain constants.
i.e.
Product.Where(e => e.Name == desiredName)
Is not supported and won't work (it will complain about using a variable on the right side of the comparison).
If you cannot avoid this kind of query, you have to .ToList() data first (this can lead to a huge result set and will probably turn up to be unconceivably slow):
Product.ToList().Where(e => e.Name == desiredName)
This will work, because now the .Where() is being applied on a List<> instead.
Another approach (I don't have data about performance, though) would be to create many queries, basically fetching the records one at a time:
// ... this is going to be a nightmare ... don't do it ...
var entities = new List<Product>();
entities.Add(Product.Where(e => e.Name == "Product 1"));
entities.Add(Product.Where(e => e.Name == "Product 2"));
Or use a QueryExpression like this (my personal favourite, because I always go late-bound)
var desiredNames = new string[]{"Product 1", "Product 2"};
var filter = new FilterExpression(LogicalOperator.And)
{
Conditions =
{
new ConditionExpression("name", ConditionOperator.In, desiredNames)
}
};
var query = new QueryExpression(Product.EntityLogicalName)
{
ColumnSet = new ColumnSet(true),
Criteria = filter
};
var records = service.RetrieveMultiple(query).Entities;
回答2:
When using QueryExpression, we can add condtionexpression for where clause. ConditionExpression takes a ConditionOperator enumerator, and we can use ConditionOperator.In. Below is how you initiate a conidtionExpression with an “In” operator, the third argument can be an array or collection.
ConditionExpression ce = new ConditionExpression("EntityName",
ConditionOperator.In, collectionObject);
Please see below for further explanation.
http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.query.conditionexpression.conditionexpression.aspx
回答3:
I do not know how to do this with Linq, as far as I know it is not possible.
It can be done with Query Expressions:
String[] productNames = new[] { "test1", "test2" };
QueryExpression products = new QueryExpression(Product.EntityLogicalName);
products.ColumnSet = new ColumnSet("name", "new_att1", "new_att2"); // fields to get
products.Criteria.AddCondition("name", ConditionOperator.In,
productNames.Cast<Object>().ToArray()); // filter by array
EntityCollection res = service.RetrieveMultiple(products);
IEnumerable<Product> opportunities = res.Entities
.Select(product => product.ToEntity<Product>()); // you can use Linq again from here
回答4:
If combining Linq and Lambda expression is ok, it can be done. First you need to create an extension method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Kipon.Dynamics.Extensions.IQueryable
{
public static class Methods
{
public static IQueryable<TSource> WhereIn<TSource, TValue>(this IQueryable<TSource> source, Expression<Func<TSource, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == source) { throw new ArgumentNullException("source"); }
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
var equalExpressions = new List<BinaryExpression>();
foreach (var value in values)
{
var equalsExpression = Expression.Equal(valueSelector.Body, Expression.Constant(value));
equalExpressions.Add(equalsExpression);
}
ParameterExpression p = valueSelector.Parameters.Single();
var combined = equalExpressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
var combinedLambda = Expression.Lambda<Func<TSource, bool>>(combined, p);
return source.Where(combinedLambda);
}
}
}
With this method in place, you can now use it against your context. First remember to import the namespace of the extension to make the method available on IQueryable:
using System.Linq;
using Kipon.Dynamics.Extensions.IQueryable;
public class MyClass
{
void myQueryMethod(CrmContext ctx, Guid[] contacts)
{
var accounts = (from a in ctx.accountSet.WhereIn(ac => ac.primarycontactid.id,contacts)
where a.name != null
select a).toArray();
}
}
There is no way you can hook into the Dynamics 365 Linq expression compiler, as far as I know, but the above code will execute in one request against the CRM, and take advantage of the fact that you do not need to consider paging and more when working with Linq.
As you can see, there whereIn clause is added with a lambda style expression, where the rest of the query is using the Linq style.
来源:https://stackoverflow.com/questions/22326423/dynamics-crm-sdk-in-operator-for-linq-with-organizationservicecontext