CRM Linq find all parents that have 0 children - linq

How can I find (preferably using CRM Linq) parent entities that have 0 children. For example how can I find all accounts that have 0 contacts.

If you are going to use the query expression route, which I would recommend then the following code will be useful
var entityAlias = "con";
var query = new QueryExpression
{
EntityName = "account",
ColumnSet = new ColumnSet(true),
Criteria =
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression(entityAlias, "contactid",ConditionOperator.Null)
}
}
LinkEntities =
{
new LinkEntity
{
EntityAlias = entityAlias,
LinkFromEntityName = "account",
LinkFromAttributeName = "accountid",
LinkToEntityName = "contact",
LinkToAttributeName = "parentcustomerid",
Columns = new ColumnSet("parentcustomerid", "contactid"),
JoinOperator = JoinOperator.LeftOuter,
}
},
};
var response = service.RetrieveMultiple(query);
var accounts = response.Entities;
In this code I have not limited the columns, this will reduce performance and you should only return the columns needed.
If there is the case for more than 5000 records are going to be returned then you will need to use paging and loop the query to find all the entities,
This can be found here:
https://msdn.microsoft.com/en-us/library/gg327917.aspx
However if you are certain you want to use LINQ then you can use the following code:
public static IEnumerable<Account> FindAccountsWithNoContacts()
{
var contactRelationship = new Relationship("contact_customer_accounts");
foreach(var account in XrmContext.AccountSet)
{
XrmContext.LoadProperty(contactRelationship);
if(!account.RelatedEntities.ContainsKey(contactRelationship)
yield return account;
}
}
My problem with the LINQ code is that all the enities, both the account and contact entities, will be loaded into memory. With large entity sets this can cause OutOfMemoryException, whereas the query expression route will pass the query to the Dynamics server to execute; which should make the execution of the code faster.

The thing you are looking for is left outer join. Which is unfortunately not possible in CRM using LINQ. However you can do it using query expression or FetchXML.
Here is a link that can help you:
https://community.dynamics.com/crm/b/gonzaloruiz/archive/2014/02/23/all-about-outer-join-queries-in-crm-2011-and-crm-2013

Related

Translating LINQ query into CRM QueryExpression while using 'select new'

I've got the following container class:
public class AssetAndItsDepositsContainer
{
public jp_asset Asset { get; set; }
public jp_deposit Deposit { get; set; }
}
Is there a way to take the following LINQ query:
from a in serviceContext.jp_assetSet
join d in serviceContext.jp_depositsSet on a.Id equals d.jp_assetid.Id
where a.statecode == jp_assetState.Active &&
a.jp_isonhold = true
select new AssetAndItsDepositsContainer()
{
Asset = a,
Deposit = d
})
.ToList();
And "translate" it by using QueryExpression?
This is what I came up with so far, but I don't know how to mimic the select new expression:
QueryExpression query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet = new ColumnSet(true);
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
// Now what?
var res = service.RetrieveMultiple(query).Entities; // gets only jp_assets
You can't access an entire LinkEntity, you can only access its attributes as AliasedValue properties on the main Entity itself.
I would simply retrieve the Id of the second record you're looking for as part of an EntityReference and then execute a Retrieve.
You can actually construct valid Entity objects from linked entities. The only requirement is that the primary key of the linked entity must be available in the result set.
So, your code snippet could be extended like this:
var query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet.AllColumns = true;
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
link.EntityAlias = "d";
link.Columns.AddColumns("jp_depositid", "jp_name");
IEnumerable<Entity> deposits = Service.RetrieveMultiple(query).Entities
.Select(e => new Entity("jp_deposit")
{
Id = (Guid)e.GetAttributeValue<AliasedValue>("d.jp_depositid").Value,
["jp_name"] = e.GetAttributeValue<AliasedValue>("d.jp_name")?.Value
});
Note
The example above will only work reliable for inner joins. In the result set the primary key (ID) for the linked entity will always be available. Therefore it is safe to get its value using the .Value syntax. All other attribute values can be retrieved using ?.Value.
For left joins you would need to filter the result set first before performing the Select.

Returning an odata IQueryable object that differs to the query options

I need to get the following code to work
public IQueryable<BankingDTO> Get(ODataQueryOptions<TillSummaryDTO> options)
{
return((IQueryable<BankingDTO>)options.ApplyTo(this._bankingService.GetBanking()));
}
I would like to query on TillSummaryDTO because it has the field "TillOpID" on it. However I would like to return BankingDTO as this is the end result which contains the group by and sum. When I run the query I receive the error "Cannot apply ODataQueryOptions of 'Bepoz.Presentation.ViewModels.TillSummaryDTO' to IQueryable of 'Bepoz.Presentation.ViewModels.BankingDTO" what is the best practice for this?
The bankingservice.GetBanking method looks like this
var query = from t in _tillSummaryRepository.Table
join w in _workStationRepository.Table on t.TillOpID equals w.WorkstationID
join s in _storeRepository.Table on w.StoreID equals s.StoreID
join v in _venueRepository.Table on s.VenueID equals v.VenueID
select new TillSummaryDTO
{
TillOpID = t.TillOpID,
Cash = t.Cash,
Workstation = new WorkstationDTO()
{
WorkstationID = w.WorkstationID,
Name = w.Name,
Store = new StoreDTO()
{
StoreID = s.StoreID,
StoreGroup = s.StoreGroup,
Name = s.Name,
Venue = new VenueDTO()
{
VenueID = v.VenueID,
VenueGroup = v.VenueGroup,
Name = v.Name,
}
}
}
};
return query.GroupBy(x => x.Workstation.Name)
.Select(x => new BankingDTO()
{
TotalCash = x.Sum(y => y.Cash),
WorkstationName = x.Key
});
The scenario you want to achieve is that you have an entity set of TillSummaryDTO that you want to query, and you would like the return type to be a collection of BankingDTO. The query for the BankingDTO is carried out by applying the query options in the URL onto TillSummaryDTO . But the fact that the BankingDTO and TillSummaryDTO are different kind of types makes it impossible achieve that in a simple Get action method, right?
This scenario can be better resolved by the function feature of the OData protocol that the function takes the TillSummaryDTO collection as input parameter, has some internal complicated logic to query for the right BankingDTO, and returns the BankingDTO instead of TillSummaryDTO.
For the concept of function in OData protocol, you can refer to this link for V4 and section "10.4.2. Functions" of this page for V3.
For implementation, this sample can be referred to for Web API OData V4 and this tutorial can be referred to for Web API OData V3.

Can LINQ be used in Dynamics CRM to get all Accounts not in a Collection?

How can a LINQ query be written to return all Accounts where the account number is not in a List?
The list is going to be pulled from an excel document.
private bool GetAccounts()
{
List<String> accountIds = new List<String>();
accountIds.Add( "[unknown]");
var query = from accounts in context.AccountSet where !accountIds.Contains(accounts.AccountNumber) select accounts;
}
It does not have to be a List.
EDIT
This is what happens when the above query runs - Is this CRM's fault?
I don't believe you can via linq. Here is the where clause limitations from the SDK.
where =>
The left side of the clause must be an attribute name and the right side of the clause must be a value. You cannot set the left side to a constant. Both the sides of the clause cannot be constants.
Supports the String functions Contains, StartsWith, EndsWith, and Equals.
You can get around these limitations by using QueryExpression or FetchExpressions. The query you want would look like this using QueryExpression. The only thing I would mention is if you are expecting a lot of record (5000+ I believe) you will most likely need to implement paging for your function as well.
private static IEnumerable<Account> GetAccounts(IOrganizationService proxy)
{
List<String> accountIds = new List<String>(new string[]{"654321", "12345"});
var results = proxy.RetrieveMultiple(new QueryExpression(Account.EntityLogicalName)
{
ColumnSet = new ColumnSet("accountid", "name", "accountnumber"),
Criteria = new FilterExpression()
{
Conditions = { new ConditionExpression("accountnumber", ConditionOperator.NotIn, accountIds) }
}
});
return results.Entities.Select(x=> x.ToEntity<Account>());
}

Not all columns retrieved on `new ColumnSet(true)` in a plug-in

I'm building the following query. For some reason, it doesn't bring to me all the fields. I've checked the spelling and when I assign values to those field, I even switched the name, leading to an exception. So I know for sure that they exist and are used. I'm adding a pre-image to the update-step with all data, just to be sure.
QueryExpression request = new QueryExpression
{
EntityName = "myLogicalName",
ColumnSet = new ColumnSet { AllColumns = true },
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.Or,
Conditions =
{
new ConditionExpression("someField", ConditionOperator.NotEqual, someValue),
new ConditionExpression("someField", ConditionOperator.Equal, somValue)
}
}
}
}
};
EntityCollection result = Service.RetrieveMultiple(request);
What can I be possibly missing?!
Its probably because; the field does not have a value or field level security is being applied.
As a side generally you should avoid using AllColumns = true
Setting the AllColumns property to true is essentially the same as doing a Select * in sql. The columns won't be adding to the ColumnSet, but they will be returned in the results of your query expression.

Calling a query from RIA Services with entities that have children created by other methods

I have this bit of code that does not work because Entity Framework doesn't recognize the CreateItemDC method. CreateItemDC is a modular private method that creates a data contract for the given Item entity. I use CreateItemDC all throughout my service whenever I need to return an Item data contract, but I can't use it here. I can realize the sequence of ProjectItems into an array or enumerable because I would have to do this to all ProjectItem entities in my database as the query criteria is specified on the client and I don't have access to it here. Do I have any better options here? It seems that RIA Services is not worth the trouble. I'm really wishing I had used plain WCF with this project.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems()
{
return from projectItem in ObjectContext.ProjectItems
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
};
}
P.S. I do love LINQ and E.F. though. :)
Well, if you want to go with plain WCF, you can, no problem, just change the code to
[Query(IsComposable=false)]
public IEnumerable<ProjectItemDC> GetProjectItems(string myParm1, string myParm2)
{
return from projectItem in ObjectContext.ProjectItems
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
}.ToArray();
}
write your own filtering/sorting logic and you're done.
Yes, you've lost WCF Ria Services dynamic query capabilities, but this is pretty much what you get with plain old WCF, isnt'it ?
If you instead need WCF Ria dynamic sorting/filtering/grouping you must take some additional steps, involving the visit of the Expression that WCF Ria Services create for you.
HTH
You can call ToArray() against ObjectContext.ProjectItems to force EF to load all the items, however, your query will no longer be composable on the client.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems()
{
return from projectItem in ObjectContext.ProjectItems.ToArray()
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
};
}
Edit:
As mentioned in your comment, it gets all of the data out of the database at once which is not ideal. In order to create the LibraryItem with your private method, you cannot compose the query on the client. Instead, you should filter within the query method and then create the array.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems(int id, string filter, object blah)
{
var projectItems = ObjectContext.ProjectItems.Where(...).ToArray();
return projectItems.Select(projectItem => new ProjectItemDC{...};
}

Resources