How To Break Up EntityReferenceCollection? - dynamics-crm

I am writing a method that would break up the EntityReferenceCollection into a specified chunk size. I have the code for a list but no idea how to do so for an EntityReferenceCollection PLEASE HELP!
public static List<List<T>> Split<T>(List<T> collection, int size)
{
var chunks = new List<List<T>>();
var chunkCount = collection.Count() / size;
if (collection.Count % size > 0)
chunkCount++;
for (var i = 0; i < chunkCount; i++)
chunks.Add(collection.Skip(i * size).Take(size).ToList());
return chunks;
}
I am trying to do a bulk delete:
string fetchXml = #" <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='new_units'>
<link-entity name='new_alterunitorder' from ='new_orderlineid' to = 'new_unitsid' >
<attribute name='new_alterunitorderid' />
<filter type='and'>
<condition attribute='new_orderdate' operator='on-or-after' value='" + startDate.ToShortDateString() + #"' />
<condition attribute='new_orderdate' operator='on-or-before' value='" + endDate.ToShortDateString() + #"' />
<condition attribute='new_orderlineid' operator='eq' uiname='" + uiName + #"' uitype='new_units' value='" + unitOrderId + #"' />
</filter>
</link-entity>
</entity>
</fetch>";
EntityCollection result = service.RetrieveMultiple(new FetchExpression(fetchXml));
var entRefCollection = new EntityReferenceCollection();
foreach (var id in result.Entities)
{
var reference = id.GetAttributeValue<EntityReference>("new_alterunitorderid");
entRefCollection.Add(reference);
}
if(entRefCollection.Count < 1000)
{
Bulk.BulkDelete(service, entRefCollection);
}
else
{
Bulk.BulkDelete(service, entRefCollection_one); //1st half of EntityReferenceCollection
Bulk.BulkDelete(service, entRefCollection_two);
}

Thanks for that update. I also just posted an answer on your other question about bulk delete.
In short, if you're going to do this yourself in code you could use the FetchXML paging cookie to download batches of 1000 records and then do multi-request batches of 1000 deletes as well.
I find the paging in LINQ to be a bit friendlier than FetchXML, so you might want to switch to a LINQ query, and page through that via the same type of syntax you're using in your List example: Skip(i * size).Take(size)
Or, as I posted on the other question you can submit a bulk delete job.

Related

Why won't records delete?

I wrote a plugin that gets the GUID IDs through fetchXML and uses Batch delete to delete the records in batches of 1000.
I debugged the plugin and it shows that the plugin executes all the way through to service.RetrieveMultiple(new FetchExpression(fetchxml)), however, the fetched records are not getting deleted. Can someone explain why? Here is the plugin code:
using System;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
/// <summary>
/// This plugin will trimm off unit orders after a contract is cancelled before the end of the contract duration
/// </summary>
namespace DCWIMS.Plugins
{
[CrmPluginRegistration(MessageNameEnum.Update,
"contract",
StageEnum.PostOperation,
ExecutionModeEnum.Asynchronous,
"statecode",
"Post-Update On Cancel Contract",
1000,
IsolationModeEnum.Sandbox,
Image1Name = "PreImage",
Image1Type = ImageTypeEnum.PreImage,
Image1Attributes = "")]
public class UnitPluginOnCancel : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Extract the tracing service for use in debugging sandboxed plug-ins.
// Will be registering this plugin, thus will need to add tracing service related code.
ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
//obtain execution context from service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// Output Parameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
//Get the before image of the updated contract
Entity PreImage = context.PreEntityImages["PreImage"];
//verify that target entity is contract and contains a cancellation date
if (entity.LogicalName != "contract" || entity.GetAttributeValue<OptionSetValue>("statecode").Value != 4)
return;
if (PreImage.GetAttributeValue<OptionSetValue>("statecode").Value == 0 || entity.Contains("cancelon"))
return;
if (PreImage.GetAttributeValue<OptionSetValue>("statecode").Value == 3 || PreImage.GetAttributeValue<OptionSetValue>("statecode").Value == 1)
return;
//obtain the organization service for web service calls.
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//Core Plugin code in Try Block
try
{
//Get Contract line start date
var startDate = PreImage.GetAttributeValue<DateTime>("cancelon");
//Get Contract Line End Date
DateTime endDate = (DateTime)PreImage["expireson"];
//Get Contract range into weekdays list
Eachday range = new Eachday();
var weekdays = range.WeekDay(startDate, endDate);
//Get Unit Order Lookup Id
EntityReference unitOrder = (EntityReference)PreImage.Attributes["new_unitorderid"];
var unitOrders = service.Retrieve(unitOrder.LogicalName, unitOrder.Id, new ColumnSet("new_name"));
var unitOrdersId = unitOrders.Id;
var uiName = unitOrders.GetAttributeValue<string>("new_name");
//Get Entity Collection to delete
string fetchXml = #" <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false' top='2000'>
<entity name='new_units'>
<link-entity name='new_alterunitorder' from ='new_orderlineid' to = 'new_unitsid' >
<attribute name='new_alterunitorderid' />
<filter type='and'>
<condition attribute='new_orderdate' operator='on-or-after' value='" + startDate.ToString("yyyy-MM-dd") + #"' />
<condition attribute='new_orderdate' operator='on-or-before' value='" + endDate.ToString("yyyy-MM-dd") + #"' />
<condition attribute='new_orderlineid' operator='eq' uiname='" + uiName + #"' uitype='new_units' value='" + unitOrdersId + #"' />
</filter>
</link-entity>
</entity>
</fetch>";
var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
var entityRefs = result.Entities.Select(e => e.GetAttributeValue<EntityReference>("new_alterunitorderid"));
var batchSize = 1000;
var batchNum = 0;
var numDeleted = 0;
while (numDeleted < entityRefs.Count())
{
var multiReq = new ExecuteMultipleRequest()
{
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = false
},
Requests = new OrganizationRequestCollection()
};
var currentList = entityRefs.Skip(batchSize * batchNum).Take(batchSize).ToList();
currentList.ForEach(r => multiReq.Requests.Add(new DeleteRequest { Target = r }));
service.Execute(multiReq);
numDeleted += currentList.Count;
batchNum++;
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occured.. Phil is responsible. ", ex);
}
catch (Exception ex)
{
tracing.Trace("An error occured: {0}", ex.ToString());
throw;
}
}
}
}
}
I tried this:
var entityGuids = result.Entities.Select(ent => ent.GetAttributeValue<Guid>("new_alterunitorderid"));
var entityRefs = entityGuids.Select(guid => new EntityReference("new_alterunitorder", guid));
Got this:
I think the error is immediately after you execute the RetrieveMultiple request
var entityRefs = result.Entities.Select(e => e.GetAttributeValue<EntityReference>("new_alterunitorderid"));
new_alterunitorderid is not an EntityReference. It is a primary key.
Instead try:
var entityRefs = result.Entities.Select(e => e.ToEntityReference());
You should also make use of your tracing object to include more output in your trace logs:
tracing.Trace("Executing query");
var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
tracing.Trace("{0} results found. Converting to EntityReferences", result.Entities.Count);
var entityRefs = result.Entities.Select(e => e.ToEntityReference());
Usually batch jobs are asynchronous. Go in Settings > System Jobs. Search for your delete job. I never used this specific syntax but the logic seems ok.

System.NullReferenceException: Object not set to an instance of an object?

I am writing a plugin that deletes records between two dates when a contract is cancelled... The records to be deleted are from the cancellation date to the end of the contract. Here is the code I am using:
using System;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
/// <summary>
/// This plugin will trimm off unit orders after a contract is cancelled before the end of the contract duration
/// </summary>
namespace DCWIMS.Plugins
{
[CrmPluginRegistration(MessageNameEnum.Update,
"contract",
StageEnum.PostOperation,
ExecutionModeEnum.Asynchronous,
"statecode",
"Post-Update On Cancel Contract",
1000,
IsolationModeEnum.Sandbox,
Image1Name = "PreImage",
Image1Type = ImageTypeEnum.PreImage,
Image1Attributes = "")]
public class UnitPluginOnCancel : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Extract the tracing service for use in debugging sandboxed plug-ins.
// Will be registering this plugin, thus will need to add tracing service related code.
ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
//obtain execution context from service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
//Get the before image of the updated contract
Entity PreImage = context.PreEntityImages["PreImage"];
//verify that the target entity represents the the contract entity has been cancelled
if (entity.LogicalName != "contract" || entity.GetAttributeValue<OptionSetValue>("statecode").Value != 4)
return;
//obtain the organization service for web service calls.
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//Core Plugin code in Try Block
try
{
//Get Contract line start date
var startDate = entity.GetAttributeValue<DateTime>("cancelon");
//Get Contract Line End Date
DateTime endDate = (DateTime)PreImage["expireson"];
//Get Contract range into weekdays list
Eachday range = new Eachday();
var weekdays = range.WeekDay(startDate, endDate);
//Get Unit Order Lookup Id
EntityReference unitOrder = (EntityReference)PreImage.Attributes["new_unitorderid"];
var unitOrderId = unitOrder.Id;
var unitOrders = service.Retrieve(unitOrder.LogicalName, unitOrder.Id, new ColumnSet("new_name"));
var uiName = unitOrders.GetAttributeValue<string>("new_name");
//Get Entity Collection to delete
string fetchXml = #" <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false' top='2000'>
<entity name='new_units'>
<link-entity name='new_alterunitorder' from ='new_orderlineid' to = 'new_unitsid' >
<attribute name='new_alterunitorderid' />
<filter type='and'>
<condition attribute='new_orderdate' operator='on-or-after' value='" + startDate.ToShortDateString() + #"' />
<condition attribute='new_orderdate' operator='on-or-before' value='" + endDate.ToShortDateString() + #"' />
<condition attribute='new_orderlineid' operator='eq' uiname='" + uiName + #"' uitype='new_units' value='" + unitOrderId + #"' />
</filter>
</link-entity>
</entity>
</fetch>";
var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
var entityRefs = result.Entities.Select(e => e.GetAttributeValue<EntityReference>("new_alterunitorderid"));
var batchSize = 1000;
var batchNum = 0;
var numDeleted = 0;
while (numDeleted < entityRefs.Count())
{
var multiReq = new ExecuteMultipleRequest()
{
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = false
},
Requests = new OrganizationRequestCollection()
};
var currentList = entityRefs.Skip(batchSize * batchNum).Take(batchSize).ToList();
currentList.ForEach(r => multiReq.Requests.Add(new DeleteRequest { Target = r }));
service.Execute(multiReq);
numDeleted += currentList.Count;
batchNum++;
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occured.. Phil is responsible. ", ex);
}
catch (Exception ex)
{
tracing.Trace("An error occured: {0}", ex.ToString());
throw;
}
}
}
}
}
I am getting a NullReferenceException on line 55... I have literally used the same line for a previous plugin without any problems.. The statecode for a cancelled contract has a value of 4 and I only want the plugin to execute when the contract has been cancelled. Here is an image of the debugging.
I have used this statement before for another plugin that acts on the contract entity and it worked fine, I don't know why this time it's not working. Here is the statement:
//verify that the target entity represents the the contract entity has been cancelled
if (entity.LogicalName != "contract" || entity.GetAttributeValue<OptionSetValue>("statecode").Value != 4)
return;
The entity you get from the input parameters may not have StateCode in it, so .Value is failing.
Maybe try entity.GetAttributeValue<OptionSetValue>("statecode") or entity.Contains("statecode") to see if there's anything there before you dereference .Value.
Since you have the PreImage, you may want to look there for the statecode.
Check to see if you are using the right profile while debugging... If you are using the wrong profile, a null reference will through on a method when it shouldn't!
Hope that helps!

Filtered subgrid - createCallbackFunctionObject throwing indescript error

I am attempting to create a filtered N:N subgrid with the code from here:
This is a Dynamics 365 Online instance if that helps. The problem I am facing though is strange in that the lookup window comes up, filters perfectly, and allows me to choose items. But when I click "add" I get a general error message.
As far as I can tell everything in the code is fine but I am unclear as to how I should proceed to debug this. My initial thought is that I could start debugging in the crmWindow.Mscrm.Utilities.createCallbackFunctionObject function but I am unclear as to how to debug that function in the global.ashx file in an online environment. My thought is that within there I may be able to get an error I can use.
Any idea?
//filters an add existing lookup view (N:N)
function addExistingFromSubGridCustom(gridTypeCode, gridControl, crmWindow, fetch, layout, viewName) {
var viewId = {DB2C6D94-48F2-E711-A2B6-00155D045E00}; // a dummy view ID
var relName = gridControl.GetParameter(relName);
var roleOrd = gridControl.GetParameter(roleOrd);
//creates the custom view object
var customView = {
fetchXml: fetch,
id: viewId,
layoutXml: layout,
name: viewName,
recordType: gridTypeCode,
Type: 0
};
var parentObj = crmWindow.GetParentObject(null, 0);
var parameters = [gridTypeCode, , relName, roleOrd, parentObj];
var callbackRef = crmWindow.Mscrm.Utilities.createCallbackFunctionObject(locAssocObjAction, crmWindow, parameters, false);
crmWindow.LookupObjectsWithCallback(callbackRef, null, multi, gridTypeCode, 0, null, , null, null, null, null, null, null, viewId, [customView]);
}
function filterAddExistingContact(gridTypeCode, gridControl, primaryEntity) {
debugger;
var crmWindow = Xrm.Internal.isTurboForm() ? parent.window : window;
var lookup = new Array();
lookup = Xrm.Page.getAttribute(new_channel).getValue();
if (lookup != null) {
var name = lookup[0].name;
var id = lookup[0].id;
var entityType = lookup[0].entityType;
}
else
{
crmWindow.Mscrm.GridRibbonActions.addExistingFromSubGridAssociated(gridTypeCode, gridControl); //default button click function
return;
}
if (primaryEntity != nxt_callreport) {
crmWindow.Mscrm.GridRibbonActions.addExistingFromSubGridAssociated(gridTypeCode, gridControl); //default button click function
return;
//Mscrm.GridRibbonActions.addExistingFromSubGridAssociated(gridTypeCode, gridControl); //default button click function
//return;
}
//fetch to retrieve filtered data
var fetch = <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"> +
<entity name="new_market"> +
<attribute name="new_marketid" /> +
<attribute name="new_name" /> +
<attribute name="createdon" /> +
<order attribute="new_name" descending="false" /> +
<filter type="and"> +
<condition attribute="new_channel" operator="eq" uiname=" + name + " uitype=" + entityType + " value=" + id + " /> +
</filter> +
</entity> +
</fetch>;
//columns to display in the custom view (make sure to include these in the fetch query)
var layout = <grid name="resultset" object="1" jump="new_name" select="1" icon="1" preview="1"> +
<row name="result" id="new_name"> +
<cell name="new_name" width="300" /> +
</row> +
</grid>;
addExistingFromSubGridCustom(gridTypeCode, gridControl, crmWindow, fetch, layout, Filtered Markets);
}
Update this line:
var crmWindow = Xrm.Internal.isTurboForm() ? parent.window : window;
To
var crmWindow = Xrm.Internal.isTurboForm() ? top.parent.window : top.window;
and update:
crmWindow.Mscrm.Utilities.createCallbackFunctionObject(

How can I use condition and filters in LinkEntity?

I want to create a QueryExpression to simulate this SQL statement
select * from A
inner join B on A.b_id=B.ID
where B.Name like "% what ever %"
this is how the FetchXML might look like
<?xml version="1.0" encoding="UTF-8"?>
<fetch distinct="true" mapping="logical" output-format="xml-platform" version="1.0">
<entity name="A">
<attribute name="ID" />
<attribute name="sce_name" />
<link-entity name="B" alias="ab" to="b_id" from="A">
<filter type="and">
<condition attribute="Name" value="% what ever %" operator="like" />
</filter>
</link-entity>
</entity>
</fetch>
How I can make this in QueryExpression LinkQuery Conditions and Filters, also I don't want to start from B since A might have its conditions too.
This is what I have tried so far
QueryExpression query = new QueryExpression("A");
query.ColumnSet.AllColumns = true;
var link = new LinkEntity()
{
JoinOperator = JoinOperator.Inner,
EntityAlias = "c",
LinkFromEntityName = "A",
LinkToEntityName = "B",
LinkFromAttributeName = "b_id",
LinkToAttributeName = "ID",
};
using (var Service = new OrganizationService("con"))
{
EntityCollection entities = Service.RetrieveMultiple(query);
}
Hopefully this should be self explanatory.
QueryExpression query = new QueryExpression("a") //Start on A
{
ColumnSet = new ColumnSet(), //Columns to retrieve from A
Criteria = new FilterExpression(LogicalOperator.And) //Conditions for A
{
Conditions =
{
new ConditionExpression()
}
},
LinkEntities =
{
//Link to B
new LinkEntity("a", "b", "aid", "bid", JoinOperator.Inner)
{
Columns = new ColumnSet(), //Columns to retrieve from B
LinkCriteria = new FilterExpression() //Conditions for B
{
Conditions =
{
new ConditionExpression()
}
}
}
}
};
In addition to James' answer, don't forget you can also query using the fetch statement you already have:
RetrieveMultipleRequest fetchRequest1 = new RetrieveMultipleRequest
{
Query = new FetchExpression(
#"<fetch distinct="true" mapping="logical" output-format="xml-platform" version="1.0">
<entity name="A">
...
</entity>
</fetch>");
};
I rarely bother writing out QueryExpressions because executing with fetch is much easier.

SOAP exception at service.Fetch

I'm trying to download attachments and notes from the Contract entity in a crm4 environment to upload to crm 2011 environment.
I get a SOAP exception at line (//String result = service.Fetch(sfetch);)
// --------- Server Name -----------
string OrgName = "USF";
string CrmSite = "http://crm4";
string TmpFolder = "C:\\TempAnnotation\\";
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = OrgName;
CrmService service = new CrmService();
service.Url = CrmSite + "/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
//service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.Credentials = new System.Net.NetworkCredential("username", "password", "domain");
string sfetch = #"<fetch mapping='logical'><entity name='contract'>
<attribute name='modifiedon' />
<attribute name='title' />
<link-entity name='annotation' from='objectid' to='contractid'>
<attribute name='annotationid'/>
<attribute name='createdon'/>
<attribute name='createdby'/>
<attribute name='documentbody'/>
<attribute name='filename'/>
<attribute name='isdocument'/>
<attribute name='mimetype'/>
<attribute name='notetext'/>
</link-entity>
</entity>
</fetch>
";
String result = service.Fetch(sfetch);
Your code works fine when I tried.
Can you add a try/catch block as such to see additional error details?
catch (System.Web.Services.Protocols.SoapException ex)
{
string error = ex.Message + " : " + ex.Detail.InnerText;
}

Resources