Dynamics Crm Bulk Update records in a transaction - c#

Requirement
I have the requirement where I want to update few fields on account
and create contact for it reading data from an API.
The number of records to be updated is around 100,000 so I want to
use either ExecuteTransactionRequest or ExecuteMultipleRequest so that I can execute all in batches.
Since I want the contact record to be created for the account updated I used the ExecuteTransactionRequest.
Problem -
The problem is batch size.
I have added the condition if request batch size count equals 500 then execute all the requests. However my batch can include
Update request for account and
Create request for contact
So it may happen that the batch may not be exact 500 and it would skip the execute request. How can I do this and make sure that contact record is created for each Updated Account correctly.
Any help would be appreciated. Thanks in Advance
Below is my code --
var requests = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection(),
ReturnResponses = returnResponses
};
foreach (var customer in customerList)
{
string custNo = customer.GetAttributeValue<string>("customernumber");
// Gets customer details from another api
var custInfo = await CustomerService.Get(custNo);
// Update the values on customer table
Entity cust = new Entity("account");
cust.Id = customer.Id;
cust["companytypecode"] = custInfo.EntityTypeCode;
cust["companytypedescription"] = custInfo .EntityTypeDescription;
var roles = custInfo.Roles.Where(c => c.RoleStatus == "ACTIVE").ToArray();
//create contact for each account
foreach(var role in roles)
{
Entity contact = new Entity("contact");
contact["FirstName"] = role.RolePerson?.FirstName;
contact["MiddleName"] = role.RolePerson?.MiddleNames;
contact["LastName"] = role.RolePerson?.LastName;
contact["AccountId"] = new EntityReference("account", customer.Id);
CreateRequest createRequest = new CreateRequest { Target = contact };
requests.Requests.Add(createRequest);
}
UpdateRequest updateRequest = new UpdateRequest { Target = cust };
requests.Requests.Add(updateRequest);
if (requests.Requests.Count == 500) // Problem is the batch size will not execute per account since it also has create request of contact. How can i make sure that each request is executed correctly
{
service.Execute(requests);
requests.Requests.Clear();
}
}
// For the last remaining accounts
if (requests.Requests.Count > 0)
{
service.Execute(requests);
}

Thank you for helping out. I resolved this with below solution. Happy to be corrected.
EntityCollection requestsCollection = new EntityCollection();
foreach (var customer in customerList)
{
string custNo= customer.GetAttributeValue<string>("customernumber");
var custInfo = await businessService.Get(custNo);
Entity cust = new Entity("account");
cust.Id = customer.Id;
cust["companytypecode"] = custInfo.EntityTypeCode;
cust["companytypedescription"] = custInfo .EntityTypeDescription;
requestsCollection.Entities.Add(cust);
var roles = custInfo.Roles.Where(c => c.RoleStatus == "ACTIVE").ToArray();
foreach(var role in roles)
{
Entity contact = new Entity("contact");
contact["FirstName"] = role.RolePerson?.FirstName;
contact["MiddleName"] = role.RolePerson?.MiddleNames;
contact["LastName"] = role.RolePerson?.LastName;
contact["AccountId"] = new EntityReference("account", customer.Id);
requests.Entities.Add(contact);
}
if (requestsCollection.Entities.Count > 500)
{
ExecuteBulkUpdate(requestsCollection);
requestsCollection = new EntityCollection();
}
}
private void ExecuteBulkUpdate(EntityCollection requestsCollection)
{
var requests = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection(),
ReturnResponses = returnResponses
};
foreach (var request in requestsCollection.Entities)
{
if (request.Id != Guid.Empty)
{
UpdateRequest updateRequest = new UpdateRequest { Target = request };
requests.Requests.Add(updateRequest);
}
else
{
CreateRequest createRequest = new CreateRequest { Target = request };
requests.Requests.Add(createRequest);
}
}
try
{
var responseForCreateRecords = (ExecuteTransactionResponse)service.Execute(requests);
int i = 0;
// Display the results returned in the responses.
foreach (var responseItem in responseForCreateRecords.Responses)
{
if (responseItem != null)
log.LogInformation(responseItem.Results["id"].ToString());
i++;
}
requests.Requests.Clear();
}
catch (FaultException<OrganizationServiceFault> ex)
{
log.LogInformation("Request failed for the {0} and the reason being: {1}",
((ExecuteTransactionFault)(ex.Detail)).FaultedRequestIndex + 1, ex.Detail.Message);
throw;
}
}

Related

How to close cases in crm 2011 programmatically

I am doing some update in crm using ssis. I tried to close some cases in crm based on certain conditions. This is my sample code in public override void Input0_ProcessInputRow(Input0Buffer Row) method.
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
// Create a Entity object of type 'case'
Entity caseEnt = new Entity("incident");
Entity incidentResolution= new Entity("incidentresolution");
incidentResolution.Attributes.Add("incidentid", new
EntityReference("incident", Row.DEVCaseGUID));
caseEnt["incidentid"] = Row.DEVCaseGUID;
//organizationservice.Update(caseEnt);
//Changes added here by //
EntityCollection collection= GetAssociatedActivities(new EntityReference("incident", Row.DEVCaseGUID))
foreach (Entity activity in collection.Entities)
{
CancelActivity(activity, organizationservice);
}
// Changes added here //
// Close the incident with the resolution.
var closeIncidentRequest = new CloseIncidentRequest
{
IncidentResolution = incidentResolution,
Status = new OptionSetValue(5)
};
organizationservice.Execute(closeIncidentRequest);
}
private EntityCollection GetAssociatedActivities(EntityReference regarding)
{
QueryExpression query = new QueryExpression { EntityName = "activitypointer", ColumnSet = new ColumnSet(new string[] { "activitytypecode" }) };
query.Criteria.AddCondition("regardingobjectid", ConditionOperator.Equal, regarding.Id);
query.Criteria.AddCondition("statecode", ConditionOperator.NotEqual, 1); //ignore completed
EntityCollection collection = organizationservice.RetrieveMultiple(query);
return collection
}
// Cancel an Activity
private static void CancelActivity(Entity entity, IOrganizationService service)
{
EntityReference moniker = new EntityReference();
if (entity.LogicalName == "activitypointer")
{
if (entity.Attributes.Contains("activityid") & entity.Attributes.Contains("activitytypecode"))
{
moniker.LogicalName = entity.Attributes["activitytypecode"].ToString();
moniker.Id = (Guid)entity.Attributes["activityid"];
SetStateRequest request = new SetStateRequest();
request.EntityMoniker = moniker;
request.State = new OptionSetValue(2);
request.Status = new OptionSetValue(-1);
SetStateResponse response = (SetStateResponse)service.Execute(request);
}
}
}
Row.DEVCaseGUID is the GUID of the Case.
statuscode is 5 for closed.
statecode is 2 for Resolved.
I tried follow this example but no success. Or is there any simple way to achieve this ?
Closing a Case in CRM is different than setting state/statuscode.
An Intermediate entity named IncidentResoultion is created when a case is closed.
You can try the following code to close the case programmatically.
Entity incidentResolution= new Entity("incidentresolution");
incidentResolution.Attributes.Add("incidentid", new
EntityReference("incident", Row.DEVCaseGUID));
// Close the incident with the resolution.
var closeIncidentRequest = new CloseIncidentRequest
{
IncidentResolution = incidentResolution,
Status = new OptionSetValue(5)
};
organizationservice.Execute(closeIncidentRequest);
Please note that a case can only be marked as Closed/Completed only if all the activititesregarding that Case are completed.
Update 09-Nov-2017: Adding code for closing related activities regarding CASE
private List<Entity> GetAssociatedActivities(EntityReference regarding)
{
QueryExpression query = new QueryExpression { EntityName = "activitypointer", ColumnSet = new ColumnSet(new string[] { "activitytypecode" }) };
query.Criteria.AddCondition("regardingobjectid", ConditionOperator.Equal, regarding.Id);
query.Criteria.AddCondition("statecode", ConditionOperator.NotEqual, 1); //ignore completed
EntityCollection activities = organizationservice.RetrieveMultiple(query);//change collection to activities
foreach (Entity activity in activities.Entities)
{
CancelActivity(activity, organizationservice);
}
}
// Cancel an Activity
private static void CancelActivity(Entity entity, IOrganizationService service)
{
EntityReference moniker = new EntityReference();
if (entity.LogicalName == "activitypointer")
{
if (entity.Attributes.Contains("activityid") & entity.Attributes.Contains("activitytypecode"))
{
moniker.LogicalName = entity.Attributes["activitytypecode"].ToString();
moniker.Id = (Guid)entity.Attributes["activityid"];
SetStateRequest request = new SetStateRequest();
request.EntityMoniker = moniker;
request.State = new OptionSetValue(2);
request.Status = new OptionSetValue(-1);
SetStateResponse response = (SetStateResponse)service.Execute(request);
}
}
}
https://www.magnetismsolutions.com/blog/roshanmehta/2012/2/16/Dynamics_CRM_2011_Closing_all_Related_Activities_for_a_Record.aspx
https://msdynamicscrmblog.wordpress.com/2013/06/18/there-are-still-open-activities-associated-with-this-case-when-resolving-a-case-in-dynamics-crm-2011/

Stripe API - C#/.NET List All Customers problems with pagination

this might be a simple answer due to my inexperience with C# and .NET. I have two Stripe Test Accounts. TL:DR; is I am essentially looking for a Customers.all solution.
The source account has all the customer, card, and charge data.
The destination account has the copied card and customer data done by Stripe Support.
I have code that loops through the pulls all the data from the source account. It then finds the customer data from the destination account using the collection of customer/card data from the source. After that it then recreates the charges from the source account into the destination account.
I am able to successfully copy the first 100 charges into the destination account using information from the source account but I am having the hardest time getting the rest of the customers.
This is what I have so far:
public static void GenerateDestinationChargeData()
{
// code to get collection of customer data from destination account
StripeConfiguration.SetApiKey(destinationTestKey);
var customerService = new StripeCustomerService();
IEnumerable<StripeCustomer> customerItems = customerService.List(
new StripeCustomerListOptions()
{
Limit = 100,
//this is what I cannot figure out, eventually to get all of the customers from the destination account
StartingAfter = customerItems.LastOrDefault().Id
}
);
// loop through collection of customers from destination acct to fetch customer charge data from source account
foreach (var i in customerItems)
{
bool isError = false;
var liveChargeService = new StripeChargeService();
StripeConfiguration.SetApiKey(sourceTestKey);
StripeList<StripeCharge> chargeItems = new StripeList<StripeCharge>();
chargeItems = liveChargeService.List(
new StripeChargeListOptions()
{
Limit = 100,
CustomerId = i.Id
}
);
// loop through customer charge data from source and re-create charge data on destination Acct
foreach (var c in chargeItems.Data)
{
StripeConfiguration.SetApiKey(sourceTestKey);
var emailReceipt = "";
Dictionary<string, string> chargeMetaData = new Dictionary<string, string>();
var onBehalfOf = "";
var transferGroup = "";
var chargeDescription = "";
var chargeCaptured = "";
var chargeCurrency = "";
var chargeStatementDescriptor = "";
if (c.ReceiptEmail != null)
{
emailReceipt = c.ReceiptEmail;
}
if (c.Metadata != null)
{
chargeMetaData = c.Metadata;
}
if (c.OnBehalfOf != null)
{
onBehalfOf = c.OnBehalfOf.ToString();
}
if (c.TransferGroup != null)
{
transferGroup = c.TransferGroup;
}
if (c.Description != null)
{
chargeDescription = c.Description;
}
if (c.Captured != null)
{
chargeCaptured = c.Captured.ToString();
}
if (c.Currency != null)
{
chargeCurrency = c.Currency;
}
if (c.StatementDescriptor != null)
{
chargeStatementDescriptor = c.StatementDescriptor;
}
try
{
var chargeOptions = new StripeChargeCreateOptions();
chargeOptions.CustomerId = i.Id;
chargeOptions.ReceiptEmail = emailReceipt;
chargeOptions.Metadata = chargeMetaData;
chargeOptions.Description = chargeDescription;
chargeOptions.Capture = c.Captured;
chargeOptions.Currency = chargeCurrency;
chargeOptions.Amount = c.Amount;
chargeOptions.StatementDescriptor = chargeStatementDescriptor;
StripeChargeService chargeService = new StripeChargeService(destinationTestKey);
StripeCharge stripeCharge = chargeService.Create(chargeOptions);
}
catch (Exception ex)
{
Utility.NotifyDevAdminException("test", ex);
isError = true;
}
if (isError) continue;
}
}
}
Thank you so much :)
Since we cannot do a Customers.all with this current Stripe API, the solution is to set an empty variable and assign it the last Customer ID in the first set of 100 that we get and continue the query from that last assigned value
var lastId = String.Empty;
if (String.IsNullOrEmpty(lastId))
{
StripeConfiguration.SetApiKey(sourceCustomerAccountAPIKey);
customerItems = customerService.List(
new StripeCustomerListOptions(){ Limit = 100 });
}
else
{
StripeConfiguration.SetApiKey(sourceCustomerAccountAPIKey);
customerItems = customerService.List(
new StripeCustomerListOptions() {
Limit = 100,
StartingAfter = lastId });
}
lastId = customerItems.LastOrDefault().Id;

To know reason of not added record in NetSuite

Trying to add new customer to NetSuite like it described in sample in manual.
private static void Main(string[] args)
{
ApplicationInfo _appInfo;
var service = new NetSuiteService();
service.CookieContainer = new CookieContainer();
_appInfo = new ApplicationInfo();
_appInfo.applicationId = "FB31C4F2-CA6C-4E5F-6B43-57632594F96";
service.applicationInfo = _appInfo;
var passport = new Passport();
passport.account = "5920356_SB9";
passport.email = "a#a.com";
var role = new RecordRef();
role.internalId = "3";
passport.role = role;
passport.password = "#sdkkr_5543";
try
{
var status = service.login(passport).status;
}
catch (Exception e)
{
Console.Out.WriteLine(e.Message);
throw;
}
var cust = new Customer();
cust.entityId = "XYZ Inc";
cust.altEmail = "aaa#aaa.aaa";
var response = service.add(cust);
Console.Out.WriteLine("response.status.isSuccess " + response.status.isSuccess) ;
Console.Out.WriteLine("response.status.isSuccessSpecified " + response.status.isSuccessSpecified);
service.logout();
}
As result I got:
response.status.isSuccess False
response.status.isSuccessSpecified True
I suppose customer was not inserted. What is wrong and how to know that?
When you call add() on the service, the response contains a status property which contains a statusDetail property. statusDetail is an array of the messages (warnings and errors) that resulted from your add(). You can loop through these to find out why saving your customer record was unsuccessful:
var response = service.add(cust);
if (!response.status.isSuccess)
{
foreach (var error in response.status.statusDetail)
{
Console.WriteLine($"Error creating customer: {error.type} {error.message}");
}
}

CRM Dynamics 2013 SDK Update Current Accounts With 2 Values

I have a scenario in CRM where I need to update existing accounts with their Vat and Registration number. There is well over 30 thousand accounts in the system. I am trying to update using the CRM SDK API but I am battling to figure out how to perform the actual update. The vat number and reg have been provided to me in a spreadsheet with their corresponding number, please note that the accounts are already in CRM so I just need to update the correct account with its Vat and Reg number, How can I do this in CRM, please advice on my code below:
public static void UpdateAllCRMAccountsWithVATAndRegistrationNumber(IOrganizationService service)
{
QueryExpression qe = new QueryExpression();
qe.EntityName = "account";
qe.ColumnSet = new ColumnSet("account", "new_vatno", "new_registrationnumber");
qe.Criteria.AddCondition("accountnumber", ConditionOperator.In,"TA10024846", "TA10028471", "TA20014015", "TA4011652", "TA4011557");
EntityCollection response = service.RetrieveMultiple(qe);
foreach (var acc in response.Entities)
{
acc.Attributes["new_vatno"] = //this is where I am struggling to figure out how I am gong to match the records up,
acc.Attributes["new_registrationnumber"] = //this is where I am struggling to figure out how I am gong to match the records up,
service.Update(acc);
}
}
How am I going to ensure that I update the correct records. I have the vat and reg numbers for the accounts in a spreadsheet, please see example image below. Can I please get advised here. Thanks.
I would load the list of VAT updates from the spreadsheet into a dictionary and then load the 30k record from CRM into memory. Then I would match them up and use ExecuteMultipleRequest to do the updates. Alternatively, you could query CRM using the account numbers (if the list is small enough.) I made the assumption you had thousands of updates to do across the record set of 30k. Note, if the Account record size was very large and couldn't be loaded into memory you would need to do account number queries.
Here is the rough code for the basic solution (I haven't tested, method should be split up, and there is minimal error handling):
public class VatInfo
{
public string RegistrationNumber;
public string TaxNumber;
public static Dictionary<string, VatInfo> GetVatList()
{
//TODO: Implement logic to load CSV file into a list. Dictionary key value should be Account Number
throw new NotImplementedException();
}
}
public class UpdateVatDemo
{
public const int maxBatchSize = 100;
public static void RunVatUpdate(IOrganizationService conn)
{
var vats = VatInfo.GetVatList();
var pagingQuery = new QueryExpression("account");
pagingQuery.ColumnSet = new ColumnSet("accountnumber");
Queue<Entity> allEnts = new Queue<Entity>();
while (true)
{
var results = conn.RetrieveMultiple(pagingQuery);
if (results.Entities != null && results.Entities.Any())
results.Entities.ToList().ForEach(allEnts.Enqueue);
if (!results.MoreRecords) break;
pagingQuery.PageInfo.PageNumber++;
pagingQuery.PageInfo.PagingCookie = results.PagingCookie;
}
ExecuteMultipleRequest emr = null;
while (allEnts.Any())
{
if (emr == null)
emr = new ExecuteMultipleRequest()
{
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = true,
ReturnResponses = true
},
Requests = new OrganizationRequestCollection()
};
var ent = allEnts.Dequeue();
if (vats.ContainsKey(ent.GetAttributeValue<string>("accountnumber")))
{
var newEnt = new Entity("account", ent.Id);
newEnt.Attributes.Add("new_vatno", vats[ent.GetAttributeValue<string>("accountnumber")].TaxNumber);
newEnt.Attributes.Add("new_registrationnumber", vats[ent.GetAttributeValue<string>("accountnumber")].RegistrationNumber);
emr.Requests.Add(new UpdateRequest() { Target = newEnt });
}
if (emr.Requests.Count >= maxBatchSize)
{
try
{
var emResponse = (ExecuteMultipleResponse) conn.Execute(emr);
foreach (
var responseItem in emResponse.Responses.Where(responseItem => responseItem.Fault != null))
DisplayFault(emr.Requests[responseItem.RequestIndex],
responseItem.RequestIndex, responseItem.Fault);
}
catch (Exception ex)
{
Console.WriteLine($"Exception during ExecuteMultiple: {ex.Message}");
throw;
}
emr = null;
}
}
}
private static void DisplayFault(OrganizationRequest organizationRequest, int count,
OrganizationServiceFault organizationServiceFault)
{
Console.WriteLine(
"A fault occurred when processing {1} request, at index {0} in the request collection with a fault message: {2}",
count + 1,
organizationRequest.RequestName,
organizationServiceFault.Message);
}
}
Updating the fetched entity is bound to fail because of its entity state, which would not be null.
To update the fetched entities, you need to new up the entity:
foreach (var acc in response.Entities)
{
var updateAccount = new Entity("account") { Id = acc.Id };
updateAccount .Attributes["new_vatno"] = null; //using null as an example.
updateAccount .Attributes["new_registrationnumber"] = null;
service.Update(acc);
}
Code below shows how I managed to get it righy. forst let me explain. I imported my records into a seperate SQL table, in my code I read that table into a list in memory, I then query CRM accounts that need to be updated, I then loop though each account and check if the account number in CRM matches the account number from my sql database, if it matches, I then update the relevant Reg no and Vat no, See code below:
List<Sheet1_> crmAccountList = new List<Sheet1_>();
//var crmAccount = db.Sheet1_.Select(x => x).ToList().Take(2);
var crmAccounts = db.Sheet1_.Select(x => x).ToList();
foreach (var dbAccount in crmAccounts)
{
CRMDataObject modelObject = new CRMDataObject()
{
ID = dbAccount.ID,
Account_No = dbAccount.Account_No,
Tax_No = dbAccount.Tax_No.ToString(),
Reg_No = dbAccount.Reg_No
//Tarsus_Country = dbAccount.Main_Phone
};
}
var officialDatabaseList = crmAccounts;
foreach (var crmAcc in officialDatabaseList)
{
QueryExpression qe = new QueryExpression();
qe.EntityName = "account";
qe.ColumnSet = new ColumnSet("accountnumber", "new_vatno", "new_registrationnumber");
qe.Criteria.AddCondition("accountnumber", ConditionOperator.In,'list of account numbers go here'
EntityCollection response = service.RetrieveMultiple(qe);
foreach (var acc in response.Entities)
{
if (crmAcc.Account_No == acc.Attributes["accountnumber"].ToString())
{
//acc.Attributes["new_vatno"] = crmAcc.VAT_No.ToString();
acc.Attributes["new_registrationnumber"] = crmAcc.Reg_No.ToString();
service.Update(acc);
}
}
}

Google Merchant Product Feed not Accepting my Feed

I am working with the GData api to import products to my merchant feed. The code is as follows:
List<ProductEntry> newEntries = new List<ProductEntry>();
foreach (Product prod in ent.Products.Where(i => !i.IsDeleted && i.IsDisplayed && i.Price > 0))
{
newEntries.Add(prod.GetNewEntry());
}
string GoogleUsername = "nope#gmail.com";
string GooglePassword = "*****";
ContentForShoppingService service = new ContentForShoppingService("MY STORE");
service.setUserCredentials(GoogleUsername, GooglePassword);
service.AccountId = "*******";
service.ShowWarnings = true;
ProductFeed pf = service.InsertProducts(newEntries);
r.Write(pf.Entries.Count.ToString());
This code is returning to me 1 entry, rather than the 400+ it should, and that 1 entry is empty with no error or warning info. Nothing shows on my merchant center dash. Any ideas on what might be occurring here?
OR - how can I get more details on what is going on?
First of all create a service account on Google Developers
Console if you don't have one already.
NuGet package Google.Apis.ShoppingContent.v2
after you do whats mentioned on the link you'll get
a ClientId
a ClientSecret
those we will be using to authenticate.
var credentials = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets() {
ClientId = "yourclientid",
ClientSecret = "yourclientsecret" },
new string[] { ShoppingContentService.Scope.Content },
"user",
CancellationToken.None).Result;
//make this a global variable or just make sure you pass it to InsertProductBatch or any function that needs to use it
service = new ShoppingContentService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "Your-app-name"
});
Now for the insert part:
private async Task<List<Product>> InsertProductBatch(IEnumerable<Product> products, ulong merchantaccountid)
{
// Create a batch request.
BatchRequest request = new BatchRequest(service);
List<Product> responseproducts = new List<Product>();
foreach (var p in products)
{
ProductsResource.InsertRequest insertRequest =
service.Products.Insert(p, merchantaccountid);
request.Queue<Product>(
insertRequest,
(content, error, index, message) =>
{
responseproducts.Add(content);
//ResponseProducts.Add(content);
if (content != null)
{
//product inserted successfully
}
AppendLine(String.Format("Product inserted with id {0}", ((Product)content).Id));
if (error != null)
{
//there is an error you can access the error message though error.Message
}
});
}
await request.ExecuteAsync(CancellationToken.None);
return responseproducts;
}
And thats all to it.

Categories

Resources