i'm using MailKit to implement an IMAP email client. In the various examples i've seen that the code to fetch message headers is this one:
var messages = client.Inbox.Fetch (0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId).ToList();
If i have correctly understood, this fetches always ALL messages.
My idea is to save in a local db messages already fetched, and then, for subsequent fetches, getting only differences.
Is there a way to accomplish this?
Thanks
Is there a way to accomplish this?
Yes, of course. The API allows you to request the information for any set of messages you want, whether you want to reference them by index or by UID.
The real question is "how?" and that all depends on two things:
The IMAP extensions supported by your IMAP server
The design of your email client and how you've chosen to populate your cache of message summary information (needed to populate your ListView or TreeView of messages in your UI).
If your IMAP server supports the QRESYNC extension, you'll want to read that specification so that you understand how best to use it as well as taking a look at the ImapFolder.Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken) method.
If the IMAP server doesn't support QRESYNC, you might want to look into taking advantage of the CONDSTORE extension. You can take advantage of this extension by using any of the Fetch() or FetchAsync() methods that take a modseq value.
In the end, your code will end up looking something like this (untested):
var uidValidity = cache.GetUidValidity ();
var known = cache.GetKnownUids ();
UniqueIdSet missing;
folder.MessageFlagsChanged += OnMessageFlagsChanged;
if (client.Capabilities.HasFlag (ImapCapabilities.QuickResync)) {
var highestModSeq = cache.GetHighestKnownModSeq ();
folder.MessagesVanished += OnMessagesVanished;
// This version of the Open() method will emit MessagesVanished and MessageFlagsChanged
// for all messages that have been expunged or have changed since the last session.
folder.Open (FolderAccess.ReadWrite, uidValidity, highestModSeq, known);
if (folder.UidValidity != uidValidity) {
// our cache is no longer valid, we'll need to start over from scratch
cache.Clear ();
cache.SetUidValidity (folder.UidValidity);
missing = folder.Search (SearchQuery.All);
} else {
// figure out which messages we are missing in our cache
missing = new UniqueIdSet (SortOrder.Ascending);
var all = folder.Search (SearchQuery.All);
foreach (var uid in all) {
if (!known.Contains (uid))
missing.Add (uid);
}
}
} else {
folder.MessageExpunged += OnMessageExpunged;
folder.Open (ImapFolder.ReadWrite);
if (folder.UidValidity != uidValidity) {
// our cache is no longer valid, we'll need to start over from scratch
cache.Clear ();
cache.SetUidValidity (folder.UidValidity);
missing = folder.Search (SearchQuery.All);
} else {
var all = folder.Search (SearchQuery.All);
// purge messages from our cache that have been purged on the remote IMAP server
foreach (var uid in known) {
if (!all.Contains (uid))
cache.Remove (uid);
}
// sync flag changes since our last session
known = cache.GetKnownUids ();
if (known.Count > 0) {
IList<IMessageSummary> changed;
if (client.Capabilities.HasFlag (ImapCapabilities.CondStore)) {
var highestModSeq = cache.GetHighestKnownModSeq ();
changed = folder.Fetch (known, highestModSeq, MessageSummaryItems.Flags | MessageSummaryItems.ModSeq | MessageSummaryItems.UniqueId);
} else {
changed = folder.Fetch (known, MessageSummaryItems.Flags | MessageSummaryItems.UniqueId);
}
foreach (var item in changed) {
// update the cache for this message
cache.Update (item);
}
}
// figure out which messages we are missing in our cache
missing = new UniqueIdSet (SortOrder.Ascending);
foreach (var uid in all) {
if (!known.Contains (uid))
missing.Add (uid);
}
}
}
// fetch the summary information for the messages we are missing
var fields = MessageSummaryItems.Full | MessageSummaryItems.UniqueId;
if (client.Capabilities.HasFlag (ImapCapabilities.CondStore))
fields |= MessageSummaryItems.ModSeq;
var newMessages = folder.Fetch (missing, fields);
foreach (var message in newMessages)
cache.Add (message);
cache.SetHighestModSeq (folder.HighestModSeq);
And then you'd need to have at least the following event handlers:
void OnMessageFlagsChanged (object sender, MessageFlagsChangedEventArgs e)
{
cache.Update (e.Index, e.Flags, e.ModSeq);
}
void OnMessageExpunged (object sender, MessageExpungedEventArgs e)
{
cache.Remove (e.Index);
}
void OnMessagesVanished (object sender, MessagesVanishedEventArgs e)
{
cache.RemoveRange (e.UniqueIds);
}
Related
The method is supposed to receive data from a server, check if new tokens have been added, and if there are, add them to the database. If the token already exists, update its status but don't add a new row in the table. This is the code I've written so far.
IEnumerable<Token> serverTokens = JsonConvert.DeserializeObject<IEnumerable<Token>>
(server.GetTokens().Content);
IEnumerable<Token> dbTokens = _tokenService.GetAllTokens();
foreach (var token in serverTokens)
{
var dbToken = dbTokens.Where(x => x.Symbol == token.Symbol).FirstOrDefault();
if (dbToken != null)
{
Token editedToken = dbToken;
editedToken.UpdatedOn = DateTime.Now;
editedToken.Active = token.Active;
_tokenService.AddToken(editedToken);
}
else
{
token.UpdatedOn = DateTime.Now;
_tokenService.AddToken(token);
}
}
dbContext.SaveChanges();
The AddToken method is just a simple AddOrUpdate operation.
public void AddToken(Token token)
{
_dbContext.Tokens.AddOrUpdate(token);
//_dbContext.SaveChanges();
}
Now, this code does what it's supposed to, however it's extremely slow. How would I go about optimizing it?
dbTokens.Where(x => x.Symbol == token.Symbol) is IEnumerable
So he will load it each time you call it on the loop.
Store in in a list before the loop
List<Token> dbTokens = _tokenService.GetAllTokens().ToList()
I started using #jstedfast Mimekit/Mailkit library, which is great by the way, to pull undeliverables using its subject line for search. I tried using the message.to, message.resentto.
how do i get that information. My first try today. I was able to get the list and the body but I just need the email. I tried using s22.imap but there's no support anymore then I discovered this. I know you're active here #jstedfast I need you help..there's no discussion tab in your github.
Thanks in advance
If you look at the raw message source, does the value of the Content-Type header match something along the lines of multipart/report; report-type=delivery-status? If so, it is very likely that this message will have a sub-part with a Content-Type header with a value of message/delivery-status. It should be the second child part of the multipart/report (the first part should be a human-readable explanation).
If so, you can cast the message/delivery-status MimeEntity to an instance of a MessageDeliveryStatus. You'll notice that the MessageDeliveryStatus class has a property called StatusGroups which is a list of multiple collections of headers (e.g. a list of HeaderList objects).
Each HeaderList contains information about a particular recipient that failed. I think what you want to do is look at the Final-Recipient header which will contain 2 pieces of information:
The address-type (should typically be "rfc822")
The mailbox of the recipient (which is what you want)
Unfortunately, MimeKit does not have any tools to parse the Final-Recipient header, but it should be trivial to locate the end of the address-type parameter in the header value by using IndexOf (';') and then you can use something like MailboxAddress.TryParse() to parse it (or you could probably just Substring() the value w/o parsing).
So, the way this might look in code is this:
string[] GetUndeliverableAddresses (MimeMessage message)
{
var report = message.Body as MultipartReport;
if (report == null)
throw new ArgumentException ("This is not a multipart/report!");
MessageDeliveryStatus status = null;
for (int i = 0; i < report.Count; i++) {
status = report[i] as MessageDeliveryStatus;
if (status != null)
break;
}
if (status == null)
throw new ArgumentException ("Did not contain a message/delivery-status!");
var undeliverables = new List<string> ();
for (int i = 0; i < status.StatusGroups.Count; i++) {
var recipient = status.StatusGroups[i]["Final-Recipient"];
int semicolon = recipient.IndexOf (';');
var undeliverable = recipient.Substring (semicolon + 1).Trim ();
undeliverables.Add (undeliverable);
}
return undeliverables.ToArray ();
}
For more information on message/delivery-status, see https://www.rfc-editor.org/rfc/rfc3464
Hope that helps.
this is what I did
foreach (var uid in inbox.Search(query))
{
var message = inbox.GetMessage(uid);
// Console.WriteLine("Subject: {0}", message.Subject);
//Console.WriteLine("Subject: {0}", message.Headers);
// Console.WriteLine("Subject: {0}", message.BodyParts);
var text = message.BodyParts;
string src = text.ElementAt(1).ToString();
int srcStart = src.IndexOf("RFC822;",0); << i used final-recipient intially
int srcEnd = src.IndexOf("Action", 0);
Console.WriteLine("Email:" + src.Substring(srcStart + 8, srcEnd - (srcStart + 8)));
Console.WriteLine(src);
//foreach (var part in message.BodyParts)
//{
// Console.WriteLine(part);
// // do something
//}
}
let me know if there could be a problem..will I get the rfc822 if the recipient inbox is full? there's no way i can test that.. I tested with emails with nonexistent domain, mailbox does not exist..with live.com, randomdomain.com,yahoo.com and gmail. gmail, on the other hand does not return any undeliverables.
I am using the Xi interface to retrieve data from an OPC .NET server. The issue that I am having though is that the server seems to be incorrectly identifying a parameter as not being historized.
Even though all the information from DeltaV indicates that the corresponding parameter for the ObjectAttributes object returned from the server should indicate that it is collecting history, the IsCollectingHistory property actually indicates that it is false.
History collection is enabled:
The parameter in question is in the history collection:
I won't include the screenshot but I can also open the historical trend for that parameter. But as you can see below, when I inspect the retrieved object while debugging, it says that it is not being historized.
Here is some of the code that I am using:
FindCriteria criteria = createCriteria(path, false);
List<Parameter> parameters = new List<Parameter>();
IEnumerable<ObjectAttributes> enumerableObject;
int i = 0;
try
{
enumerableObject = iContext.FindObjects(criteria, 50);
}
catch (System.ServiceModel.FaultException)
{
//This error is thrown when no data is returned.
return null;
}
A few lines down, I then do some object initialization for my Parameter object, assigning it the properteries from the object that was received from the server. It is not shown below but I then add my Parameter object to a collection if it is being historized. It never gets added to the collection because the IsCollectingHistory property is always false.
enumerableObject = enumerableObject.Skip(1);
foreach (ObjectAttributes oa in enumerableObject)
{
Parameter _parameter = new Parameter
{
IsHistorized = oa.IsCollectingHistory,
IsLeaf = oa.IsLeaf
};
//...
Any ideas on where I am going wrong?
Edit:
After trying MotteAndBailey's answer, an error is thrown at the call to AddNewDataObjectToDataJournalList. The message associated with it is "The OPC HDA Create Browse failed".
Below is a screenshot of the error in a message box when using HDAprobe:
Some info on properties in OPC.NET -
The Xi server wraps the OPCDA and OPCHDA servers. The item properties (attributes) available on each server and the means of accessing them using Classic (DCOM) OPC vary considerably.
OPCDA
The OPCDA server provides a method for determining item properties:
IOPCItemProperties::GetItemProperties()
This call provides the client with a way to read all an item’s attribute values using the item ID (string path for the item in the Server’s address space) and propertyID. The Xi server uses this call when performing a FindObjects() call. FindObjects() returns all the properties exposed by the OPCDA server for an item.
OPCHDA
The OPCHDA server has a method for determining item properties, but it requires the item handle not the ItemID/path:
IOPCHDA_SyncRead::ReadAttribute()
As a result, the FindObjects() call for an OPCHDA server through Xi only returns the item ID property. The other attributes are set to defaults. To determine what the actual values of the other properties are a user must add the item to a list and call ReadAttribute() on the specific property they wish to see.
In summary, the OPCDA server works pretty much like you would expect and returns the object properties in a single method call; however, the OPCHDA server requires an additional step to get all the object properties.
The sample code will produce an Xi Journal list with only the on-scan history items.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xi.Client.Base.API;
using Xi.Client.Base;
using Xi.Contracts;
using Xi.Contracts.Data;
using Xi.Contracts.Constants;
using System.ServiceModel.Description;
using Xi.Client.CommonDialogs;
using System.Runtime.Serialization;
namespace ConsoleFindObjectsHDA
{
public class DotNetSupport
{
IXiEndpointDiscovery iEndpointDiscovery;
IXiContext iContext;
IXiEndpointBase readEndpoint;
ServiceEndpoint RMSvcEndpt;
//the list of items the user is trying to read
IXiDataJournalList iDataJournalList;
public void GetSomeData()
{
//get a connection to an OPC.NET server
if (Connect())
{
//we have to have a list to add a tag to and hold returned datasets
if (CreateJournalList())
{
//now use the list to add items and read their attributes - only "good" ones will be left on the list
ListAllHDAItemsOnScan();
if (iDataJournalList.Count() > 0)
{
//at this point we <should> have a DataJournalList containing only the HDA items on scan
//we can use the normal data read methods to get history for the items if we wish
// <do some history thing here>
}
else
{
Console.WriteLine("\nThere were no points on-scan in the historian");
}
Console.WriteLine("\nPress <return> to exit program");
Console.ReadLine();
}
////clean up if we have open connections/contexts
Cleanup();
}
}
//we will use FindObjects to browse all the leaves in the HDA server, then add them one-by-one to a datalist
//when we query their on-scan property and it is true we leave them on the list
//...if not, we remove them (giving us a list of the good HDA points)
void ListAllHDAItemsOnScan()
{
FindCriteria criteria = GetLeafCriteria();
IEnumerable<ObjectAttributes> enumerableObject;
try
{
//ask the server for a list of leaves...up to 50 max returned in this call
enumerableObject = iContext.FindObjects(criteria, 50);
//for each string itemID: add it to the list, read the attribute, and remove it from the list if not on-scan
foreach (ObjectAttributes oa in enumerableObject)
{
//we do not have to commit this because we have indicated the operation is NOT prep-only
Console.WriteLine("Adding OPCHDA item {0}.", oa.InstanceId.LocalId);
IXiHistoricalDataObject ixObject = iDataJournalList.AddNewDataObjectToDataJournalList(oa.InstanceId, false);
//we are getting the CURRENT (from NOW -to- NOW) item status
FilterCriterion fc1 = GetFilterCriteriaDateTime(DateTime.Now);
//tell the server what property (attribute) we want to read
List<TypeId> lstp = new List<TypeId>();
TypeId typeit = new TypeId("", "", DVServerAttributes.DELTAV_DVCH_ON_SCAN.ToString());
lstp.Add(typeit);
//read the property from the server
iDataJournalList.ReadJournalDataProperties(fc1, fc1, ixObject, lstp);
//find the current item and check it for "on-scan"
if (ixObject != null)
{
if (ixObject.PropertyValues.First().PropertyValues.UintValues.First() == 1)
{
Console.WriteLine("OPCHDA item {0} is on-scan.\n", oa.InstanceId.LocalId);
}
else
{
if (ixObject.PrepForRemove())
{
Console.WriteLine("OPCHDA item {0} is not on-scan. Removing item.\n", oa.InstanceId.LocalId);
iDataJournalList.CommitRemoveableElements();
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception in FindObjects(). The exception is:{0}\n", ex.Message);
}
}
//create a filtercriterion for a specific date time - this is an EQUAL (not > or <) comparison operator
public FilterCriterion GetFilterCriteriaDateTime(DateTime dtChosenTime)
{
//simple timestamp filter which is used by the read call
FilterCriterion filterCriterion = null;
// this is a timestamp
string filterOperand = FilterOperandNames.Timestamp;
//make the given time with UTC/Local set to local time just to be sure
DateTime dtTmp1 = DateTime.SpecifyKind(dtChosenTime, DateTimeKind.Local);
object comparisonValue = dtTmp1;
//timestamp equal to this one
uint oper = FilterOperator.Equal;
//create the filter
filterCriterion = new FilterCriterion()
{
OperandName = filterOperand,
Operator = oper,
ComparisonValue = comparisonValue,
};
return filterCriterion;
}
//create a filtercriterion for leaves (OPCHDA items)
public FilterCriterion GetLeafFilterCriterion()
{
//simple filter for leaves
FilterCriterion filterCriterion = null;
// what this criterion applies to
string filterOperand = FilterOperandNames.BranchOrLeaf;
//Must equal "LEAF" to match
uint oper = FilterOperator.Equal;
//create the filter
filterCriterion = new FilterCriterion()
{
OperandName = filterOperand,
Operator = oper,
ComparisonValue = "LEAF",
};
return filterCriterion;
}
//set up the FindCriteria search of the server
public FindCriteria GetLeafCriteria()
{
FindCriteria findCriteria = null;
findCriteria = new FindCriteria();
//our browse starts at the root - NULL means "continue browsing from where you are"
findCriteria.StartingPath = new ObjectPath("//", "HDA");
//a list of OR-ed filter criteria (we have only one)
ORedFilters orthefilters = new ORedFilters();
//the FilterCriteria list (there is only one criterion)
orthefilters.FilterCriteria = new List<FilterCriterion>();
orthefilters.FilterCriteria.Add(GetLeafFilterCriterion()); //we want leaves
//add our OR-ed filter to the filterset filters list (whew!)
findCriteria.FilterSet = new FilterSet();
findCriteria.FilterSet.Filters = new List<ORedFilters>();
findCriteria.FilterSet.Filters.Add(orthefilters);
return findCriteria;
}
//connect to the OPC.NET server and get a read endpoint
public bool Connect()
{
//set this to point to your OPC.Net server
string serverUrl = "http://localhost:58080/XiServices/ServerDiscovery";
bool bReturnVal = false;
try
{
Console.WriteLine("Getting Endpoint Discovery from server:\n{0}\n", serverUrl);
//This class is used to locate a server and obtain its list of ServiceEndpoints.
iEndpointDiscovery = new XiEndpointDiscovery(serverUrl) as IXiEndpointDiscovery;
//we have the server...now check for endpoints
//there should always be TCP endpoints for a DeltaV OPC.NET server so we do not search HTTP and Named Pipes to find one
//and we do not consider choosing the fastest option between the three (TCP/HTTP/NamedPipes). We just use the TCP/IP one.
// GetServiceEndpointsByBinding searches the list of endpoints on the XiEndpointDiscovery connection with the specified contractType and binding type.
// We use the ResourceManagement endpoint to find the the other open endpoints on the server (some might be disabled)
IEnumerable<ServiceEndpoint> resourceEndpoints = iEndpointDiscovery.GetServiceEndpointsByBinding(typeof(IResourceManagement).Name, typeof(System.ServiceModel.NetTcpBinding));
//use the first (probably only) resource endpoint for TCP/IP to open a context between client and server
if ((resourceEndpoints != null) && (resourceEndpoints.Count() > 0))
{
var serviceEndpoints = resourceEndpoints as IList<ServiceEndpoint> ?? resourceEndpoints.ToList();
//pick the first RM endpoint we found
RMSvcEndpt = ((IList<ServiceEndpoint>)serviceEndpoints).First();
//Open the Context using the RM endpoint and some other values including timeout, what we want to read (HDA), and the GUID for this context
Console.WriteLine("Opening Client Context with Initiate\n");
iContext = XiContext.Initiate(RMSvcEndpt,
iEndpointDiscovery.ServerEntry,
300000,
(uint)ContextOptions.EnableJournalDataAccess, //HDA
(uint)System.Threading.Thread.CurrentThread.CurrentCulture.LCID,
Guid.NewGuid().ToString());
if (iContext != null)
{
//find a read endpoint using the XiEndpointDiscovery connection
IEnumerable<ServiceEndpoint> readseps = iEndpointDiscovery.GetServiceEndpointsByBinding(typeof(IRead).Name, RMSvcEndpt.Binding.GetType());
//if we found at least one read endpoint, connect the context to it
readEndpoint = null;
if (readseps != null)
{
Console.WriteLine("Adding Read endpoint to Context\n");
ServiceEndpoint sep = readseps.ElementAt<ServiceEndpoint>(0);
readEndpoint = iContext.OpenEndpoint(sep, 30000, new TimeSpan(5000));
if (readEndpoint != null)
{
bReturnVal = true; //everything went OK
}
else
{
Console.WriteLine("Unable to add Read endpoint to Context\n");
bReturnVal = false; //failed
}
}
}
else
{
Console.WriteLine("Unable to open Client Context\n");
bReturnVal = false;
}
}
}
catch (Exception)
{
bReturnVal = false;
}
return (bReturnVal);
}
public bool CreateJournalList()
{
bool retval = false;
try
{
//create a new list of HDA objects for this read
// update buffer
// rate rate filterset(not used here)
iDataJournalList = iContext.NewDataJournalList(1000, 1000, null);
if (iDataJournalList != null)
{
//we need to add the list to a read endpoint to give it data access
iDataJournalList.AddListToEndpoint(readEndpoint);
//enable the list so we can connect the items we add to it and read data
iDataJournalList.EnableListUpdating(true);
retval = true;
}
}
catch (Exception)
{
retval = false;
}
return retval;
}
public void Cleanup()
{
if (iContext != null)
{
iContext.Dispose();
}
}
} //class DotNetSupport
}
namespace ConsoleFindObjectsHDA
{
public class DVServerAttributes
{
//some DeltaV-specific OPCHDA attributes
public static uint DELTAV_DESC = 2147483650;
public static uint DELTAV_ENG_UNITS = 2147483651;
public static uint DELTAV_EU100 = 2147483666;
public static uint DELTAV_EU0 = 2147483667;
public static uint DELTAV_DVCH_LAST_DOWNLOAD = 2147483682;
public static uint DELTAV_DVCH_ON_SCAN = 2147483683;
public static uint DELTAV_NAMED_SET = 2147483698;
}
}
We have an email queue table in the database. It holds the subject, HTML body, to address, from address etc.
In Global.asax every interval, the Process() function is called which despatches a set number of emails. Here's the code:
namespace v2.Email.Queue
{
public class Settings
{
// How often process() should be called in seconds
public const int PROCESS_BATCH_EVERY_SECONDS = 1;
// How many emails should be sent in each batch. Consult SES send rates.
public const int EMAILS_PER_BATCH = 20;
}
public class Functions
{
private static Object QueueLock = new Object();
/// <summary>
/// Process the queue
/// </summary>
public static void Process()
{
lock (QueueLock)
{
using (var db = new MainContext())
{
var emails = db.v2EmailQueues.OrderBy(c => c.ID).Take(Settings.EMAILS_PER_BATCH);
foreach (var email in emails)
{
var sent = Amazon.Emailer.SendEmail(email.FromAddress, email.ToAddress, email.Subject,
email.HTML);
if (sent)
db.ExecuteCommand("DELETE FROM v2EmailQueue WHERE ID = " + email.ID);
else
db.ExecuteCommand("UPDATE v2EmailQueue Set FailCount = FailCount + 1 WHERE ID = " + email.ID);
}
}
}
}
The problem is that every now and then it's sending one email twice.
Is there any reason from the code above that could explain this double sending?
Small test as per Matthews suggestion
const int testRecordID = 8296;
using (var db = new MainContext())
{
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
db.ExecuteCommand("DELETE FROM tblLogs WHERE ID = " + testRecordID);
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
}
using (var db = new MainContext())
{
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
}
Returns when there is a record:
Found
Found
Not Found
If I use this method to clear the context cache after the delete sql query it returns:
Found
Not Found
Not Found
However still not sure if it's the root cause of the problem though. I would of thought the locking would definitely stop double sends.
The issue that your having is due to the way Entity Framework does its internal cache.
In order to increase performance, Entity Framework will cache entities to avoid doing a database hit.
Entity Framework will update its cache when you are doing certain operations on DbSet.
Entity Framework does not understand that your "DELETE FROM ... WHERE ..." statement should invalidate the cache because EF is not an SQL engine (and does not know the meaning of the statement you wrote). Thus, to allow EF to do its job, you should use the DbSet methods that EF understands.
for (var email in db.v2EmailQueues.OrderBy(c => c.ID).Take(Settings.EMAILS_PER_BATCH))
{
// whatever your amazon code was...
if (sent)
{
db.v2EmailQueues.Remove(email);
}
else
{
email.FailCount++;
}
}
// this will update the database, and its internal cache.
db.SaveChanges();
On a side note, you should leverage the ORM as much as possible, not only will it save time debugging, it makes your code easier to understand.
I have written a function to update Default Price List for all the Active Products on the CRM 2013 Online.
//The method takes IOrganization service and total number of records to be created as input
private void UpdateMultipleProducts(IOrganizationService service, int batchSize, EntityCollection UpdateProductsCollection, Guid PriceListGuid)
{
//To execute the request we have to add the Microsoft.Xrm.Sdk of the latest SDK as reference
ExecuteMultipleRequest req = new ExecuteMultipleRequest();
req.Requests = new OrganizationRequestCollection();
req.Settings = new ExecuteMultipleSettings();
req.Settings.ContinueOnError = true;
req.Settings.ReturnResponses = true;
try
{
foreach (var entity in UpdateProductsCollection.Entities)
{
UpdateRequest updateRequest = new UpdateRequest { Target = entity };
entity.Attributes["pricelevelid"] = new EntityReference("pricelevel", PriceListGuid);
req.Requests.Add(updateRequest);
}
var res = service.Execute(req) as ExecuteMultipleResponse; //Execute the collection of requests
}
//If the BatchSize exceeds 1000 fault will be thrown.In the catch block divide the records into batchable records and create
catch (FaultException<OrganizationServiceFault> fault)
{
if (fault.Detail.ErrorDetails.Contains("MaxBatchSize"))
{
var allowedBatchSize = Convert.ToInt32(fault.Detail.ErrorDetails["MaxBatchSize"]);
int remainingCreates = batchSize;
while (remainingCreates > 0)
{
var recordsToCreate = Math.Min(remainingCreates, allowedBatchSize);
UpdateMultipleProducts(service, recordsToCreate, UpdateProductsCollection, PriceListGuid);
remainingCreates -= recordsToCreate;
}
}
}
}
Code Description : There are around 5000 active product records in the System. So I am updating Default Price List for all of them using above code.
But, I am missing here something so that, it has updated only 438 records. It loops through the While statement correctly, but it is not updating all of them here.
What should be the Batchsize when we run this function for the First Time?
Any one can help me here?
Thank you,
Mittal.
You pass remainingCreates as the batchSize parameter but your code never references batchSize so you are just going to reenter that while loop every time.
Also, I'm not sure how you are doing all your error handling but you need to update your catch block so that it doesn't just let FaultExceptions pass-through if they don't contain a MaxBatchSize value. Right now, if you take a FaultException regarding something other than batch size it will be ignored.
{
if (fault.Detail.ErrorDetails.Contains("MaxBatchSize"))
{
var allowedBatchSize = Convert.ToInt32(fault.Detail.ErrorDetails["MaxBatchSize"]);
int remainingCreates = batchSize;
while (remainingCreates > 0)
{
var recordsToCreate = Math.Min(remainingCreates, allowedBatchSize);
UpdateMultipleProducts(service, recordsToCreate, UpdateProductsCollection, PriceListGuid);
remainingCreates -= recordsToCreate;
}
}
else throw;
}
Instead of reactive handling, i prefer proactive handling of the MaxBatchSize, this is true when you already know what is MaxMatchSize is.
Following is sample code, here while adding OrgRequest to collection i keep count of batch and when it exceeds I call Execute and reset the collection to take fresh batch.
foreach (DataRow dr in statusTable.Rows)
{
Entity updEntity = new Entity("ABZ_NBA");
updEntity["ABZ_NBAid"] = query.ToList().Where(a => a.NotificationNumber == dr["QNMUM"].ToString()).FirstOrDefault().TroubleTicketId;
//updEntity["ABZ_makerfccall"] = false;
updEntity["ABZ_rfccall"] = null;
updEntity[cNBAttribute.Key] = dr["test"];
req.Requests.Add(new UpdateRequest() { Target = updEntity });
if (req.Requests.Count == 1000)
{
responseWithResults = (ExecuteMultipleResponse)_orgSvc.Execute(req);
req.Requests = new OrganizationRequestCollection();
}
}
if (req.Requests.Count > 0)
{
responseWithResults = (ExecuteMultipleResponse)_orgSvc.Execute(req);
}