问题
I wrote this method with much help from StackO
, but I've been unable to find an a was to improve on this.
Right now, I iterate through each message in an Office 365 inbox, traverse its attachment collection, and save each file to disk if it's an Excel workbook. This is currently working, but results in a ton of calls to Exchange. The inbox size can get pretty large, and this takes a long time to run as a result, with each call clocking in at around 0.5 seconds.
If I've counted right, the number of calls scales as (n / 100) + 2n.
- n is the number of messages in the inbox (2 calls per message)
- 100 is the pageSize, as I haven't seen anything larger than this (one call every page)
Note: Typically (99.9999%) there's only one attachment per message, but I added the inner loop for CYA purposes. Might be some memory overhead, but little-to-no scaling factor.
I was wondering if there's a better way, with fewer web service calls. Is there an EWS method that could just download attachments in bulk? I'm pretty new to EWS, so I'm sure I'm lacking understanding here. What I want to do is reduce the pageSize
and Load(path)
a batch of files to disk, reducing the scale.
Any suggestions for improvement on the code below, with respect to reducing EWS calls, would be greatly appreciated.
public void SaveAttachmentsFromInbox(string[] extensionFilter = null)
{
// Default to excel files
extensionFilter = extensionFilter ?? new[] { ".xls", ".xlsx" };
// Config for traversing inbox
int offset = 0;
int pageSize = 100;
ItemView view = new ItemView(pageSize, offset, OffsetBasePoint.Beginning);
view.PropertySet = PropertySet.FirstClassProperties;
FindItemsResults<Item> findResults;
// Loop through the inbox
// and save all attachments of the designated file types
bool more = true;
var fileCount = 0;
while (more)
{
findResults = service.FindItems(WellKnownFolderName.Inbox, view);
// Load each sheet's data into an Object
foreach (var item in findResults.Items)
{
//get FirstClassProperties
item.Load(view.PropertySet);
string vendor = GetVendor(EmailMessage.Bind(service, item.Id));
messageIds.Add(item.Id.ToString());
// Save files to disk
foreach (FileAttachment file in item.Attachments)
{
string fileExtension = file.Name.Substring(file.Name.IndexOf('.'), file.Name.Length - file.Name.IndexOf('.'));
if (extensionFilter.Contains(fileExtension))
{
var fullPath = Path.Combine(path, file.Name);
attachmentInfo.Add(fullPath, vendor);
// Loads attachment and saves to disk
file.Load(fullPath);
fileCount++;
Console.Write("\rFiles received... {0} ", fileCount);
}
}
}
Console.WriteLine(); // Next line
more = findResults.MoreAvailable;
// Page through inbox if more messages remain
if (more)
{
view.Offset += pageSize;
}
}
Console.WriteLine(attachmentInfo.Count + " Excel Attachment Downloads successful.\n");
}
回答1:
As well as using AQS to cut down the recordset that is returned from the server you should make use of the batchs command to first get the Item properties using LoadPropertiesForItems (replaces the Bind in your code for each items) and then you can batch the Attachment download using GetAttachments (you need to make sure your using version 2.2 of the EWS Managed API) this will mean for example a batch of 100 item your making one FindItems Calls, a batch GetItem call and a Batch GetAttachment Call rather the 1 * 100 * 100 if you use Bind and Load. eg something like
ItemView ivItemView = new ItemView(100);
PropertySet flLevel = new PropertySet(BasePropertySet.IdOnly);
ivItemView.PropertySet = flLevel;
FindItemsResults<Item> faItems = service.FindItems(WellKnownFolderName.Inbox, "attachment:.xlsx OR attachment:xls", ivItemView);
PropertySet slLevel = new PropertySet(BasePropertySet.FirstClassProperties);
if (faItems.Items.Count > 0)
{
service.LoadPropertiesForItems(faItems, slLevel);
}
List<Attachment> atAttachments = new List<Attachment>();
foreach (Item itItem in faItems.Items)
{
foreach (Attachment atAttachment in itItem.Attachments)
{
if (atAttachment is FileAttachment)
{
string fileExtension = atAttachment.Name.Substring(atAttachment.Name.IndexOf('.'), atAttachment.Name.Length - atAttachment.Name.IndexOf('.'));
if (extensionFilter.Contains(fileExtension))
{
atAttachments.Add(atAttachment);
}
}
}
}
service.GetAttachments(atAttachments.ToArray(), BodyType.HTML,null);
foreach (FileAttachment FileAttach in atAttachments)
{
Console.Write(FileAttach.Name);
System.IO.File.WriteAllBytes("c:\\export\\" + FileAttach.Name, FileAttach.Content);
//save off
}
回答2:
If you are targeting Exchange 2010 or later versions, you could use a FindItem with Advanced Query Syntax:
ItemView view = new ItemView(100);
FindItemsResults<Item> results = service.FindItems(folder, "Has attachment:true", view);
foreach (Item item in results.Items)
{
if (item is EmailMessage)
{
// Get the item and FileAttachments in the same way.
}
}
I have not tried this myself, but it is possible that you could get a even better result with the AQS Has attachment:true AND .xlsx
.
来源:https://stackoverflow.com/questions/33898269/how-can-i-reduce-ews-calls-when-downloading-attachments-from-an-inbox