I am exploring the "QueryExpression" mechanism used to Retrieve data via the Dynamics CRM SDK, and I think I have hit a problem / limitiation with the SDK, but I would like to ascertain that for certain..
Given this desired SQL:
Select C.firstname, C.lastname
FROM contact C
INNER JOIN customeraddress A on C.contactid = A.parentid
WHERE
((C.firstname = 'Max' AND C.lastname = 'Planck') OR (C.firstname = 'Albert' AND C.lastname = 'Einstein'))
OR
A.Line1 = 'The secret moonbase'
I cannot appear to translate the filter criteria above (the where clause) into the equivalent SDK conditions / filterexpressions etc.
As you can see, I want to query:-
contact, joined to customeraddress (thats simple, just add a link entity to the query expression),
where the contact is either Albert Einstein, or Max Planck (Again, that is simple, add FilterExpressions to the QueryExpression)
OR the customeraddress 'line1' equals 'the secret moonbase' (This is the problematic bit, as soon as I append filter criteria to the LinkEntity, Dynamics uses an "AND" conjunction with the criteria / filters on the main entity.
So the problem I have described in point 3 above, means I can't query dynamics for:
(Albert Einstein Or Max Planck) or anyone who lives at the secret moonbase.
Is this is a current limtation of the SDK?
Ok, I have discovered the answer to this, thanks in part to #mwrichardsone who prompted me to explore how the Dynamics Crm Linq query provider does it, I was then able to work backwards from there..
So here is the equivalent Linq query expression which works (I am using the CrmOrganisationServiceContext):-
var contactsQuery = from c in orgService.CreateQuery("contact")
join a in orgService.CreateQuery("customeraddress") on (Guid)c["contactid"] equals (Guid)a["parentid"]
where (((string)c["firstname"] == "Max" && (string)c["lastname"] == "Planck")
|| ((string)c["firstname"] == "Albert" && (string)c["lastname"] == "Einstein"))
|| (string)a["line1"] == "The secret moonbase"
select c;
I then found this article which explains how you can convert linq query to a Query Expression or Fetch Xml: http://pogo69.wordpress.com/2012/04/05/crm-linq-provider-converting-expressions-to-queryexpression-andor-fetchxml/
Once i applied that technique I was able to see what the equivalent QueryExpression looks like.. and basically, the bit that I was missing (key insight) is that when you add a ConditionExpression you can set it's "EntityName". This means you can add a ConditionExpression to a filter group thats on the parent / main entity, even though the condition is actually for an attribute thats present on a link entity (in this case customeraddrress line1). I was assuming you had to add the condition to the linkentity that had that particular attribute - which is also what #Henk van Boeijen did in his answer - and that did not give the correct results.
So the final working QueryExpression looks like this (notice the condition for address line 1 is not added to the address link entity, its added to the filter group on the main entity, and it has an "entity name" set to the alias of the link entity)
var orgService = serviceProvider.GetOrganisationService();
using (orgService as IDisposable)
{
var query = new QueryExpression("contact");
query.ColumnSet.AddColumn("firstname");
query.ColumnSet.AddColumn("lastname");
// so link in customer address.
query.AddLink("customeraddress", "contactid", "parentid", JoinOperator.Inner);
var addressLink = query.LinkEntities[0];
addressLink.EntityAlias = "A";
addressLink.IncludeAllColumns();
// conditions for max planck
var firstName1Condition = new ConditionExpression("firstname", ConditionOperator.Equal, "Max");
var lastname1Condition = new ConditionExpression("lastname", ConditionOperator.Equal, "Planck");
// Groups those conditions using an "AND" conjunction.
var maxPlankFilter = new FilterExpression(LogicalOperator.And);
maxPlankFilter.AddCondition(firstName1Condition);
maxPlankFilter.AddCondition(lastname1Condition);
// conditions for albert einstein
var firstname2Condition = new ConditionExpression("firstname", ConditionOperator.Equal, "Albert");
var lastname2Condition = new ConditionExpression("lastname", ConditionOperator.Equal, "Einstein");
// Groups those conditions using an "AND" conjunction.
var albertEinsteinFilter = new FilterExpression(LogicalOperator.And);
albertEinsteinFilter.AddCondition(firstname2Condition);
albertEinsteinFilter.AddCondition(lastname2Condition);
// could optionally chain the 2 filters so we get Albert's contitions chained (using AND) to max's conditions
// albertEinsteinFilter.AddFilter(maxPlankFilter);
// conditions for address line 1 moonbase
var addressLine1Filter = new FilterExpression(LogicalOperator.And);
var line1Condition = new ConditionExpression("A", "line1", ConditionOperator.Equal, "The secret moonbase");
addressLine1Filter.AddCondition(line1Condition);
// add filters to query
// ensures each filter that we add to our queries criteria is chained together using an OR.
query.Criteria.FilterOperator = LogicalOperator.Or;
query.Criteria.AddFilter(albertEinsteinFilter);
query.Criteria.AddFilter(maxPlankFilter);
query.Criteria.AddFilter(addressLine1Filter);
var results = orgService.RetrieveMultiple(query);
int resultCount = 0;
foreach (var r in results.Entities)
{
resultCount++;
Console.WriteLine(string.Format("{0} {1} {2}", (string)r["firstname"], (string)r["lastname"], (string)((AliasedValue)r["A.line1"]).Value));
}
Console.WriteLine("There were " + resultCount + " results..");
}
Side Note: See #Henk van Boeijen's post below if you would like to see a shorter syntax for building a query expression. If productivity is truly your concern however, I would have to echo the comment from #Nicknow below and suggest that you seriously take a look at using the Linq query mechanism for performing CRM queries.
Also #Henk van Boeijen has pointed out that my answer is based on a feature that only appears in the 2013 SDK, and doesn't appear to be in prior versions. I haven't checked this personally, but that information is probably very useful for you to know especially if you are not using the latest versions of the SDK.
It is actually pretty straightforward; use the LogicalOperator and the LinkEntity.
I would recommend adding the DISTINCT predicate.
private IEnumerable<Entity> QueryExpression(IOrganizationService service)
{
var query = new QueryExpression("contact");
query.Distinct = true;
query.ColumnSet.AddColumns("firstname", "lastname");
query.Criteria.FilterOperator = LogicalOperator.Or;
var f1 = query.Criteria.AddFilter(LogicalOperator.And);
f1.AddCondition("firstname", ConditionOperator.Equal, "Max");
f1.AddCondition("lastname", ConditionOperator.Equal, "Planck");
var f2 = query.Criteria.AddFilter(LogicalOperator.And);
f2.AddCondition("firstname", ConditionOperator.Equal, "Albert");
f2.AddCondition("lastname", ConditionOperator.Equal, "Einstein");
var link = query.AddLink("customeraddress", "contactid", "parentid");
link.EntityAlias = "ca";
query.Criteria.AddCondition("ca", "line1", ConditionOperator.Equal, "The secret moonbase");
var response = service.RetrieveMultiple(query);
return response.Entities;
}
It is important to note that this query uses a new feature added in Dynamics CRM 2013. It does not work in Dynamics CRM 2011, because in that version it is not possible to specify an entityname (or its alias) in the ConditionExpression.
Related
It may be I'm still thinking in the Linq2Sql mode, but I'm having a hard time translating this to OrmLite.
I have a customers table and a loyalty card table.
I want to get a list of customers and for each customer, have a list of express cards.
My strategy is to select customers, join to loyalty cards, group by whole customer table, and then map the cards to a single property on customer as a list.
Things are not named by convention, so I don't think I can take advantage of the implicit joins.
Thanks in advance for any help.
Here is the code I have now that doesn't work:
query = query.Join<Customer, LoyaltyCard>((c, lc) => c.CustomerId == lc.CustomerId)
.GroupBy(x => x).Select((c) => new { c, Cards = ?? What goes here? });
Edit: I thought maybe this method:
var q = db.From<Customer>().Take(1);
q = q.Join<Customer, LoyaltyCard>().Select();
var customer = db.SelectMulti<Customer,LoyaltyCard>(q);
But this is giving me an ArgumentNullException on parameter "key."
It's not clear from the description or your example code what you're after, but you can fix your SelectMulti Query with:
var q = db.From<Customer>()
.Join<Customer, LoyaltyCard>();
var results = db.SelectMulti<Customer,LoyaltyCard>(q);
foreach (var tuple in results)
{
Customer customer = tuple.Item1;
LoyaltyCard custCard = tuple.Item2;
}
I am creating a custom module in Orchard , I would like to create a query programmatically.
string queryName= "Product";
var item = _orchardServices.ContentManager.New("Query");
item.As<TitlePart>().Title =queryName;
_orchardServices.ContentManager.Create(item, VersionOptions.Draft);
if (!item.Has<IPublishingControlAspect>() && !item.TypeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable)
_orchardServices.ContentManager.Publish(item);
var queryPart = item.As<QueryPart>();
queryPart.ContentItem.ContentType = queryName;
string desc =" filter for the query";
string contentType = "CommonPart.ChannelID.";
var filterGroupRecord = new FilterGroupRecord();
var filterRecord = new FilterRecord()
{
Category = "CommonPartContentFields",
Type = contentType,
Position = 0,
};
filterRecord.State = "<Form><Description>" + desc + "</Description><Operator>Equals</Operator><Value>ChannelId</Value></Form>";
filterGroupRecord.Filters.Add(filterRecord);
queryPart.FilterGroups.Insert(0, filterGroupRecord);
the problem is that:I want set a filters of the query,not a filters group.
could you tell me how to improve my code?
Database structure and class declarations make it impossible. Why do you need it?
Update:
I means that you must use FilterGroupRecord at least one.
But when Query published that Filter Group will be created automatically if query have not yet Filter Group (see at QueryPartHandler). You should add your filters to this group. And not needed to create new group.
var existingFilterGroup = queryPart.FilterGroups[0];
existingFilterGroup.Filters.Add(filterRecord);
Update 2:
To avoid problems with draftable query (and several other potential problems Orchard CMS: Adding default data to fields and then querying them) it is better to move the calling Publish method to the end of your code and other part of your code should be left unchanged. And in your case would be better if you will always publish your query without checking IPublishingControlAspect and Draftable.
I have a requirement to sync some entities (account, lead, contact etc) to a database table outside of the crm database but on the same server. I am looking for a supported way for doing this. Here's what I have tried, that don't work:
I first created table in the outside database that matches the schema from dbo.account (view). Then I wrote post create, post update, post assign and post delete plugins to create, update or delete the record in the outside table (using ADO.Net). I have written the plugin in the most generic way so that it can be registered for any entity with minimum changes to the plugin (by not hardcoding the field names). Doing it this way, the problem I am running into is with the fields that are foreign key to other tables. Eg. in dbo.account, there are fields like PrimaryContactId and PrimaryContactIdName, PreferredSystemUserId and PreferredSystemUserIdName, ParentAccountId and ParentAccountIdName etc. In the input parameters for the plugin, the xxxxId fields are available when they are updated, but not the 'xxxxIdName' fields. Because of which I am not able to 'sync' the table as is.
Is there a solution to make my plugin solution work?
Is there a better supported way for having a sync table?
Thanks in advance,
PS: 1. The data sync has to be in real time
PS: 2. Here is my function to get the query that does the update
private static string PrepareUpdateQuery(ITracingService tracingService, IEnumerable<KeyValuePair<string, object>> attributeCollection, string entityName, string entityIdName)
{
var query = "Update MainDb.MSCRM." + entityName + " set ";
foreach (KeyValuePair<string, object> keyValuePair in attributeCollection)
{
tracingService.Trace("Key: {0}", keyValuePair.Key);
if (keyValuePair.Key != entityIdName && keyValuePair.Key != "modifiedonbehalfby")
{
query = query + keyValuePair.Key + " = ";
if (keyValuePair.Value == null)
query = query + "null, ";
else
{
var typeOfValue = keyValuePair.Value.GetType().Name;
tracingService.Trace("typeOfValue: {0}", typeOfValue);
switch (typeOfValue)
{
case "EntityReference":
query = query + "'" + ((EntityReference)keyValuePair.Value).Id + "', ";
break;
case "OptionSetValue":
query = query + ((OptionSetValue)keyValuePair.Value).Value + ", ";
break;
case "BooleanManagedProperty":
query = query + (((BooleanManagedProperty)keyValuePair.Value).Value ? "1" : "0") + ", ";
break;
default:
query = query + "'" + keyValuePair.Value + "', ";
break;
}
}
}
}
return query;
}
If all you're after is the name of the entity that is an attribute on your currently executing plugin, the EntityReference object has a Name property that should contain that name. If it doesn't you you can query CRM with the id and logical name to get any value that you're looking for on the referenced entity.
Edit 1
If you're just moving the data, why even bother setting the referenced name? I'd removed those names from your database table, and just create a view that looks up the corresponding entity's name. It's what CRM is doing. It also makes your other database more normalized. IE. If you update the name of an entity that is referenced by another entity, you will have to search for and update all of those names...
the xxxIdName fields are just a helper for the views really, you can easily figure out what they
should contain.
For example, say you have an account 'some company' with a primary contact called 'bob bobson'.
when processing the account entity the primarycontactId will be a guid and the primarycontactIdName will be 'bob bobson', the accountIdName will be 'some company'.
easiest way to do this in your plugin is to look up the related entity and get the value from there - 90% of the time it's just the name field.
you also need to consider however if you are doing the right thing in using the CRM schema, perhaps it would be better to copy only the fields you need and use your own schema for the sync table.
UPDATE: just saw your code, you are overwritting the value contained in query and not setting it back to the base query, so you will get odd results/errors on the second pass through the foreach
If you're dead set on putting the related entity name in the primary entity table you can always grab it like this:
var entityEntityRef = (EntityReference)keyValuePair.Value;
var relatedEntity = service.Retrieve(entityRef.LogicalName, entityRef.Id, new ColumnSet(true));
Now relatedEntity as all the attributes available. You'll mostly be looking for the Name field, but some entities are different, like contact which uses the full name field I believe.
You can, in fact, register a single plugin for all entities (checking, of course, that the one that's firing the message is in the list of treated ones).
IEnumerable<String> supportees = new String[]{ "account", "contact" };
if(!supportees.Any(element
=> element == targetLogicalName))
return;
As for the linked entities, you have three choices.
Just skip them. Not full data sync but easies to implement.
Store the guids only. Data sync is instance-wide - limited but moderately easy.
Get all the linked data. Full information but a recursive PIA to develop.
How can I add a filter condition to the linked entity (for example email in this case)?
Added filter condition to the link criteria which is giving me the duplicate rows.
The equivalent sql query should look like this.
select distinct OpportunityId
from Opportunity o
left join Email e on e.RegardingObjectId = o.OpportunityId
where o.StateCode = 1 and o.StatusCode = 3
and e.RegardingObjectId is null
But the QueryExpression class is doing the following way.
select distinct opportunityid
from Opportunity o
left join Email e
on e.RegardingObjectId = o.OpportunityId
and e.RegardingObjectId is null
where o.StateCode = 1 and o.StatusCode = 3
The code:
ClientCredentials Credentials = new ClientCredentials();
Credentials.Windows.ClientCredential
= System.Net.CredentialCache.DefaultNetworkCredentials;
Uri OrganizationUri = ""
Uri HomeRealmUri = null;
OrganizationServiceProxy orgService
= new OrganizationServiceProxy(OrganizationUri, HomeRealmUri, Credentials, null);
IOrganizationService _service = (IOrganizationService)orgService;
QueryExpression query = new QueryExpression();
query.Distinct = true;
query.EntityName = "opportunity";
query.ColumnSet = new ColumnSet(true);
FilterExpression filter1 = new FilterExpression();
filter1.FilterOperator = LogicalOperator.And;
filter1.AddCondition("statuscode", ConditionOperator.Equal,3);
filter1.AddCondition("statecode", ConditionOperator.Equal, 1);
query.Criteria = filter1;
LinkEntity linkEntity1 = new LinkEntity();
linkEntity1.JoinOperator = JoinOperator.LeftOuter;
linkEntity1.LinkFromEntityName = "opportunity";
LinkEntity1.LinkFromAttributeName = "opportunityid";
linkEntity1.LinkToEntityName = "email";
linkEntity1.LinkToAttributeName = "regardingobjectid";
query.LinkEntities.Add(linkEntity1);
FilterExpression filter2 = new FilterExpression();
The issue is here at this condition. I could use filter on the LinkCriteria but not on the query since it is linked entity.
filter2.AddCondition("regardingobjectid", ConditionOperator.Null);
query.LinkEntities[0].LinkCriteria = filter2;
EntityCollection result = _service.RetrieveMultiple(query);
Console.WriteLine(result.Entities.Count());
Console.ReadKey();
I'm not sure if the query posted is what you are looking for...
If it is, then you should be able to remove filter 2 and add to filter 1
filter1.AddCondition("opportunityid", ConditionOperator.Null);
But comparing the RegardingObjectId to both NULL and the OpportunityID with an AND operation shouldn't ever be true.
I had similar problems adding conditions to linked entities. I found that i could do it using the dynamics 2013 sdk, but you would have to see if you could use the 2013 sdk against a 2011 dynamics. Please see Microsoft Dynamics Crm Sdk - Is this query possible?
The basic difference with the 2013 SDK is you can add a condition to the filter but give it an entity name which is for a linked entity. This means you don't actually add the condition to the link entity itself.
I also show in that link how to use the Linq provider to write the query which is another alternative you may want to try.
Any idea how to inject values to the Enterprise Keywords column of a List / Doc Lib Item using code?
Tried the following, it didn't give any error, but that column wouldn't update, while the Title did.
using (var site = new SPSite("http://testweb"))
{
using (var web = site.OpenWeb("testsite1"))
{
var list = web.Lists["testlist1"];
var item = list.AddItem();
item["Title"] = string.Format("Injected from code on {0}", DateTime.Now.ToString());
item["Enterprise Keywords"] = "1;#Opera|4eed0518-9676-4afc-be20-9027b3b69e42";
item.Update();
}
}
In this code, Opera keyword has been added previously, I've checked it against the TaxonomyHiddenList list as well using code to extract the correct ID and IdForTerm (the GUID).
What am I missing here?
To add a taxonomy field value the approach is a little bit different. Please try:
TaxonomyField entKeyword = (TaxonomyField)item.Fields["Enterprise Keywords"];
TaxonomyFieldValue term = new TaxonomyFieldValue("1;#Opera|4eed0518-9676-4afc-be20-9027b3b69e42");
entKeyword.SetFieldValue(item,term);
in stead of:
item["Enterprise Keywords"] = "1;#Opera|4eed0518-9676-4afc-be20-9027b3b69e42";