How to release Outlook objects correctly? - c#

I just can't release my Outlook MailItems. After opening 200 Mails the Exchange Sever returns the maximum open Emails is reached.
I'm remove my UserProperty from all selected Mail.
My Code:
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
MailItem mi = (MailItem)selection;
UserProperty up = mi.UserProperties.Find("MyProp");
if (up != null)
{
up.Delete();
//##################################
// I also tried :
//----------------------------------
// Marshal.ReleaseComObject(up);
// up = null;
//----------------------------------
}
mi.Save();
//##################################
// I also tried :
//----------------------------------
// mi.Close(OlInspectorClose.olDiscard);
//----------------------------------
// I don't know if this loop is necessary, but I have found it somewhere on the web
while (Marshal.ReleaseComObject(mi) > 0);
mi = null;
//##################################
// I also tried :
//----------------------------------
// GC.Collect();
// GC.WaitForPendingFinalizers();
//----------------------------------
}
}
Any idea what's wrong?

You can try this: Instead of defining a new MailItem everytime within the For loop, can you define mi outside the For loop or even in your class level, and reuse it for each mailitems? e.g:
MailItem mi;
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
mi= (MailItem)selection;
// your other code...
}
}
mi=null;
GC.Collect();
GC.WaitForPendingFinalizers();
EDIT:
Try to create local variable for each references e.g:
Outlook.Explorer myExplorer=Application.ActiveExplorer();
Outlook.Selection mySelection=myexplorer.Selection;
foreach (var selection in mySelection)
{
}
myExplorer=null;
mySelection=null;
//....
EDIT-2:
IF you are using Outlook 2010 check this:
Outlook 2010 addin selection not clearing

I believe its any kind of bug like Bolu said.
Again much thanks for your help Bolu.
I'm now using following workaround:
List entryids = new List();
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
MailItem mi = selection as MailItem;
if (mi != null)
{
// For any reason it's not possible to change the mail here
entryids.Add(mi.EntryID);
Marshal.ReleaseComObject(mi);
mi = null;
}
}
foreach (string id in entryids)
{
MailItem mi = Globals.ThisAddIn.Application.ActiveExplorer().Session.GetItemFromID(id);
// My changes on the mail
mi.Save();
Marshal.ReleaseComObject(mi);
mi = null;
}

You are using multiple dot notation (mi.UserProperties.Find), which means the compiler creates an implicit variable to hold the result of the mi.UserProperties call; you cannot explicitly release that variable. That object holds a reference to its parent MailItem object.
Store it in an explicit variable and release it explicitly using Marshal.ReleaseComObject. Ditto for the UserProperty up variable.
Also, do not use foreach with Outlook collections - that loop holds a reference to all items until the loop exits. Use a for loop and release the items explicitly on each step of the loop immediately after you are done with that item
Selection selectedItems = Globals.ThisAddIn.Application.ActiveExplorer().Selection;
for (int i = 1; i <= selectedItems.Count; i++)
{
object selection = selectedItems[i];
MailItem mi = selection as MailItem;
if (mi != null) //can have items other than MailItem
{
UserProperties props = mi.UserProperties;
UserProperty up = props.Find("MyProp");
if (up != null)
{
...
Marshal.ReleaseComObject(up);
};
Marshal.ReleaseComObject(props);
Marshal.ReleaseComObject(mi);
}; //if
Marshal.ReleaseComObject(selection);
}; //for

Try to replace foreach with a for loop and do the following
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Also remove all the reference to any outlook COM object that you might be using.

Just put the releasing method outside the main if condition. Items get referenced even when you just loop through them with the foreach loop.
Write
Marshal.ReleaseComObject(mi)
right before the last "}". This works for me. I read that generally you have to release every COM object explicitly.

Related

why does the IS operator enters an IF statement when false?

I most be missing something here,
see in the image i made of my debug session.
(items[i] is MailItem) is FALSE according the debugger, but still it enters the if statement.
What am I missing here ?
.
For reference, here is the full code of this method
private MailItem GetMailBySubject(DateTime dateReceived, string subject)
{
MailItem Result = null;
Microsoft.Office.Interop.Outlook.Application OutlookIns = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace olNamespace = OutlookIns.GetNamespace("MAPI");
MAPIFolder myInbox = olNamespace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Items items = myInbox.Items;
int count = items.Count;
MailItem mail = null;
int i = 1; //DO NOT START ON 0
while ((i < count) && (Result == null))
{
if (items[i] is MailItem)
{
mail = (MailItem)items[i];
if ((mail.ReceivedTime.ToString("yyyyMMdd hh:mm:ss") == dateReceived.ToString("yyyyMMdd hh:mm:ss")) && (mail.Subject == subject))
{
Result = mail;
}
}
i++;
}
return Result;
}
This SO answer explains a bit about why you are seeing the IF condition getting passed even when the expression inside it is false. Apparently it's an issue with the debugger and multiple threads. Also, it suggests a workaround to prevent this issue by using lock. Hope it helps.
I made a workaround using the link provided by Wai Ha Lee. I had to alter it though, because testing if an item is MailItem still behaved strangely.
So I copy the items first into a separate list, and make sure only items of type MailItem are in that list.
The only way I got this filtered is by using try...catch, I would still like a better way and I am still curious why the test if (items[i] is MailItem) behaves so strangely.
List<MailItem> ReceivedEmail = new List<MailItem>();
foreach (var testMail in items)
{
try
{
ReceivedEmail.Add((MailItem)testMail);
}
catch (System.Exception ex)
{
;
}
}
After this I can use the list ReceivedEmail without the check on MailItem.

c# outlook MailItem add/remove recipient of type BCC

I want to programmatically add with C# recipients to an existing/composing MailItem. When I add a recipient like this:
Microsoft.Office.Interop.Outlook.Inspector inspector = Globals.ThisAddIn.Application.ActiveInspector();
Microsoft.Office.Interop.Outlook.MailItem item = inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test.user");
mRecipient.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
it appears in the TO field of the MailItem.
When I do something like this:
item.BCC = "test.user";
it appears correclty...
Is there a way to to add the recipient with the first method (Type.olBCC) and show it up in the BCC mail field (2nd snippet)?
I want to go this way because then I can iterate through all recipients and remove some when a special condition is called.
The problem is when I remove the added BCC recipient with
item.BCC = "";
all recpients were deleted in the BCC field.
Apparently we are all missing the point: https://msdn.microsoft.com/en-us/vba/outlook-vba/articles/recipients-object-outlook
According to this
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test.user");
mRecipient..Type = olCC // at a guess here olBCC would be BCC...
We can modify the list as a list.. :)
Note: I couldnt find olCC in my list.. but maybe I need to look harder.
Note2! Found it.
OlMailRecipientType. has olTo, olOriginator, olCC and olBCC
There you go. so mRecipient.Type = OlMailRecipientType.olBCC should do the trick
The following - opened a new mail item with joe.bloggs in bcc:
olApplication = new Microsoft.Office.Interop.Outlook.Application();
m = olApplication.CreateItem(OlItemType.olMailItem);
Recipient r = m.Recipients.Add("joe.bloggs");
r.Type = (int)OlMailRecipientType.olBCC;
m.Display(true);
Having saved a draft and worked out how to get hold of it as 1 item apparently isnt items[0] ..
the following also works:
Microsoft.Office.Interop.Outlook.MAPIFolder folderDrafts = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderDrafts);
if (folderDrafts.Items.Count > 0)
{
m = (Microsoft.Office.Interop.Outlook.MailItem)folderDrafts.Items[1];
Recipient r = m.Recipients.Add("joe.bloggs");
r.Type = (int)OlMailRecipientType.olBCC;
m.Display(true);
}
As the user BugFinder mentioned there is a workaround neccessary since the UI adds the BCC recipient to the olTO field. I solved this with first adding a dummy, then add the BCC and remove the dummy:
Microsoft.Office.Interop.Outlook.Inspector inspector = Globals.ThisAddIn.Application.ActiveInspector();
Microsoft.Office.Interop.Outlook.MailItem item = inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
//Create dummy recipient object for UI bug
Microsoft.Office.Interop.Outlook.Recipient dummy =
item.Recipients.Add("bugDummy");
dummy.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
//Create the BCC object that will be used
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test#user.de");
mRecipient.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
//iterate through the recipients and delete the dummy
foreach (Microsoft.Office.Interop.Outlook.Recipient recipient in item.Recipients)
{
if (string.Compare(recipient.Name, "bugDummy", true) == 0)
{
recipient.Delete();
break;
}
}

Running out of memory looping through mail items

Hi I have a Outlook com addin that is doing some simple searching tricks for me. I am part way through putting it together but I am having issues with it running out of memory. The process is very simple and basically loops through an outlook folder checking each mailItem for a match. given the loop reinitialize the variables each time I would have expected the garbage collector to keep up but when I watch the memory it loses ~10m/sec until the system is out of memory and I get unhandled exceptions.
This is part of the code
private void FindInFolder(Outlook.MAPIFolder FolderToSearch)
{
Outlook.MailItem mailItem;
Outlook.MAPIFolder ParentFolder;
int counter = 0;
StatusBar.Text = "Searching in Folder " + FolderToSearch.FolderPath + "/" + FolderToSearch.Name;
StatusBar.Update();
this.Update();
foreach (COMObject item in FolderToSearch.Items)
{
counter++;
if (counter % 100 == 0)
{
StatusBar.Text = FolderToSearch.FolderPath + "/" + FolderToSearch.Name + " item " + counter + " of " + FolderToSearch.Items.Count;
StatusBar.Update();
if (counter % 1000 == 0)
{
GC.Collect();
}
}
if (item is Outlook.MailItem)
{
mailItem = item as Outlook.MailItem;
if (IsMatch(mailItem))
{
if (mailItem.Parent is Outlook.MAPIFolder)
{
ParentFolder = mailItem.Parent as Outlook.MAPIFolder;
ResultGrd.Rows.Add(mailItem.EntryID, ParentFolder.FolderPath, mailItem.SenderName, mailItem.Subject, mailItem.SentOn);
}
}
}
mailItem = null;
}
}
Which calls
private Boolean IsMatch(Outlook.MailItem inItem)
{
Boolean subBool = false;
Boolean NameBool = false;
try
{
if (null != inItem)
{
if (SubjectTxt.Text != "")
{
if (inItem.Subject.Contains(SubjectTxt.Text))
{
subBool = true;
}
}
else
{
subBool = true;
}
if (NameTxt.Text != "")
{
if (inItem.Sender != null)
{
if (inItem.Sender.Name.Contains(NameTxt.Text))
{
NameBool = true;
}
}
}
else
{
NameBool = true;
}
return subBool && NameBool;
}
}
catch (System.Runtime.InteropServices.COMException ce)
{
if (ce.ErrorCode == -2147467259)
{
//DO nothing just move to the next one
}
else
{
MessageBox.Show("Crash in IsMatch error code = " + ce.ErrorCode + " " + ce.InnerException);
}
}
return false;
}
Please excuse all the error catching part at the bottom and the GC.collect they are some of my attempts to work out what is wrong and free up memory.
Note also FindInFolder is called by a new thread so I can interact with results while it continues to search.
What I have tried so far:
Making variables local to function not class so the are retrievable by G, however the most used variable in 'item' as it is part of foreach it must be declared that way.
every 1000 mailItems do a manual GC, this made no difference at all.
For some reason it needs alot of memory just looping through the items and GC never frees them up.
Please also note I am using netoffice not VSTO for Com addin.
First of it all: This is NetOffice code and you dont need Marshal.ReleaseComObject in NetOffice. (Moreover, its useless to call ReleaseComObject here) Use Dispose() for the instance instead.
Keep in your mind: NetOffice handle COM proxies for you (Thats why its allowed to use two 2 dots in NetOffice).
In your case its internaly stored as:
// FolderToSearch
-- Items
--Enumerator
-- Item
-- Item
-- ....
Use item.Dispose() at the end of each loop to remove/free the item instance or use the following after foreach
FolderToSearch.Dipose()
// dispose folder instance and all proxies there comes from
FolderToSearch.DisposeChildInstances()
// dispose all proxies there comes from but keep the folder instance alive
Next:
The Items enumerator here is a custom enumerator(given by NetOffice)
However it works fine with small amount of items but dont do this in a more
heavier scenario(may exchange server and thousands of items). The local workstation/program can't handle this in memory. For this reason Microsoft provides only the well kown GetFirst/GetNext pattern. In NetOffice it looks like:
Outlook._Items items = FolderToSearch.Items;
COMObject item = null;
do
{
if (null == item)
item = items.GetFirst() as COMObject;
if (null == item)
break;
// do what you want here
item.Dispose();
item = items.GetNext() as COMObject;
} while (null != item);
This should works as well.
When working with COM objects from C#, there were 2 tricks that I used to prevent memory and COM object reference counts from building:
Use System.Runtime.InteropServices.Marshal.ReleaseComObject() to release COM objects as you are done with them. This forces a COM "release" on the object.
Do not foreach to iterate through a COM collection. foreach holds on to an enumerator object, which prevents other objects from being released.
So, instead of this:
foreach (COMObject item in FolderToSearch.Items)
{
// ....
}
do this:
Items items = FolderToSearch.Items;
try
{
for (int i = 0; i < items.Count; ++i)
{
COMObject item = items[i];
try
{
// work
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(item);
}
}
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(items);
}
These tips helped me reduce memory and object consumption.
Disclaimer: I cannot attest to whether this is good practice or not though.
First, I'd recommend using the Find/FindNext or Restrict methods of the Items class instead of iterating through all items in the folder. For example:
Sub DemoFindNext()
Dim myNameSpace As Outlook.NameSpace
Dim tdystart As Date
Dim tdyend As Date
Dim myAppointments As Outlook.Items
Dim currentAppointment As Outlook.AppointmentItem
Set myNameSpace = Application.GetNamespace("MAPI")
tdystart = VBA.Format(Now, "Short Date")
tdyend = VBA.Format(Now + 1, "Short Date")
Set myAppointments = myNameSpace.GetDefaultFolder(olFolderCalendar).Items
Set currentAppointment = myAppointments.Find("[Start] >= """ & tdystart & """ and [Start] <= """ & tdyend & """")
While TypeName(currentAppointment) <> "Nothing"
MsgBox currentAppointment.Subject
Set currentAppointment = myAppointments.FindNext
Wend
End Sub
See the following articles for more information and sample code:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Also you may find the AdvancedSearch method of the Application class helpful. The key benefits of using the AdvancedSearch method are listed below:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Second, I always suggest releasing underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about that in the Systematically Releasing Objects article.
If you want to use GC, you need to call the Collect and WaitForPendingFinalizers methods twice.
Matt, you still don't release all objects in the code. For example:
for (int i = 0; i < FolderToSearch.Items.Count; ++i)
{
COMObject item = FolderToSearch.Items[i];
The Items property of the Folder class returns an instance of the corresponding class which should be released after. I see at lease two lines of code where the reference counter is increased.

Optimizing Outlook Add-Ins

My Outlook 2007 Addin works very well, until the point where I have to move mass amounts of messages.
The list of mail items is obtained like this
Items itemObj = lNamespace.GetFolderFromID(Settings.Default.InputFolder).Items;
List<MailItem> totalMailItems = new List<MailItem>();
foreach (MailItem mailItem in itemObj)
{
totalMailItems.Add(mailItem);
}
//Dispose of itemObj
itemObj = null;
MAPIFolder blueFold = lNamespace.GetFolderFromID(Settings.Default.BlueFolder);
MAPIFolder greenFold = lNamespace.GetFolderFromID(Settings.Default.GreenFolder);
MAPIFolder orangeFold = lNamespace.GetFolderFromID(Settings.Default.OrangeFolder);
MAPIFolder redFold = lNamespace.GetFolderFromID(Settings.Default.RedFolder);
foreach (MailItem mailItem in totalMailItems)
{
MailItem xMail = mailItem;
MessageRelevance mRel = new MessageRelevance();
mRel = Process_MailItem(ref xMail);
xMail.Save();
switch(mRel)
{
case MessageRelevance.Red:
xMail.Move(redFold);
_lvl2++;
break;
case MessageRelevance.Orange:
xMail.Move(orangeFold);
_lvl1++;
break;
case MessageRelevance.Blue:
xMail.Move(blueFold);
_nullLev++;
break;
case MessageRelevance.Green:
xMail.Move(greenFold);
_lvl0++;
break;
}
xMail = null;
}
The reason I set xMail to mailItem is because I can't use mailItem as a reference, it's readonly. The rest of the program works great now, I'm just trying to figure out how to move these items faster. Do I have to call Save before? Or is that just an extra call?
I am not sure how many mail items you are actually moving, so I will assume alot.
The one thing that may be the issue is that the Save() method could be the bottleneck in your code. I had a similar type issue with an excel add-in that copied files to several locations. The solution to improve the speed and keep excel responsive was to use Asynchronous Delegate Invocation as described in Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads.
So in your example I would wrap the contents of the loop on totalMailItems. Note the code below may not be 100% correct but I hope its enough of an idea and guidance to help you out.
private delegate void SaveEmail(MailItem mailItem);
foreach (MailItem mailItem in totalMailItems)
{
SaveEmail save = SaveMailItem;
IAsyncResult saveResult = save.BeginInvoke(mailItem, SaveCallBack, "MailItem Saved")
xMail = null;
}
private void SaveCallBack(IAsyncResult result)
{ // do stuff here if you want to... }
private void SaveMailItem(MailItem mailItem)
{
MailItem xMail = mailItem;
MessageRelevance mRel = new MessageRelevance();
mRel = Process_MailItem(ref xMail);
xMail.Save();
switch(mRel)
{
case MessageRelevance.Red:
xMail.Move(redFold);
_lvl2++;
break;
case MessageRelevance.Orange:
xMail.Move(orangeFold);
_lvl1++;
break;
case MessageRelevance.Blue:
xMail.Move(blueFold);
_nullLev++;
break;
case MessageRelevance.Green:
xMail.Move(greenFold);
_lvl0++;
break;
}
}

copy list items from one list to another in sharepoint

In Sharepoint how can you copy a list item from one list to another list
eg copy from "List A" to "List B" (both are at the root of the site)
I want this copying to occur when a new list item is added to "List A"
I tried using the CopyTo() method of an SPListItem inside the ItemAdded event receiver but couldnt figure out the url to copy to.
Here is the code I use. Pass it a SPlistItem and the name of the destination list as seen in Sharepoint(Not the URL). The only restriction is that both list must be in the same site:
private SPListItem CopyItem(SPListItem sourceItem, string destinationListName) {
//Copy sourceItem to destinationList
SPList destinationList = sourceItem.Web.Lists[destinationListName];
SPListItem targetItem = destinationList.Items.Add();
foreach (SPField f in sourceItem.Fields) {
//Copy all except attachments.
if (!f.ReadOnlyField && f.InternalName != "Attachments"
&& null != sourceItem[f.InternalName])
{
targetItem[f.InternalName] = sourceItem[f.InternalName];
}
}
//Copy attachments
foreach (string fileName in sourceItem.Attachments) {
SPFile file = sourceItem.ParentList.ParentWeb.GetFile(sourceItem.Attachments.UrlPrefix + fileName);
byte[] imageData = file.OpenBinary();
targetItem.Attachments.Add(fileName, imageData);
}
return targetItem;
}
Indeed as Lars said, it can be tricky to move items and retain versions and correct userinfo. I have done similar things with that before so if you need some code examples, let me know through a comment and can supply you with some guidance.
The CopyTo method (if you decide to go with that) need an absolute Uri like:
http://host/site/web/list/filename.doc
So, if you are performing this in an event receiver you need to concatinate a string containing the elements needed. Something like (note that this can be done in other ways to):
string dest=
siteCollection.Url + "/" + site.Name + list.Name + item.File.Name;
Copying and moving files, items and folders in SharePoint can be tricky if you want to retain all metadata, timestamps, author info and version history. Take a look a CopyMove for SharePoint - it also has a Web Service API.
There's many tools on the market for copying a list item to another list (avepoint, metavis, etc.) but they are pretty expensive if you're planning to do this on only one list.
If you can do this manually once a week for example, look at the following tool : http://en.share-gate.com/sharepoint-tools/copy-move-sharepoint-list-items-with-metadata-and-version-history
Here is a powershell equivalent of Sylvian's that does allow for cross-site copy. His code could be modified similarly as well...
param([string]$sourceWebUrl, [string]$sourceListName, [string]$destWebUrl, [string]$destListName)
$sourceWeb = get-spweb $sourceWebUrl;
$sourceList = $sourceWeb.Lists[$sourceListName];
$destWeb = get-spweb $destWebUrl;
$destList = $destWeb.Lists[$destListName];
$sourceList.Items |%{
$destItem = $destList.Items.Add();
$sourceItem = $_;
$sourceItem.Fields |%{
$f = $_;
if($f.ReadOnlyField -eq $false -and $f.InternalName -ne "Attachments" -and $sourceItem[$f.InternalName] -ne $null){
$destItem[$f.InternalName] = $sourceItem[$f.InternalName];
}
}
$destItem.Update();
}
To use, copy and past to a file copy-listitems.ps1 and run using Sharpoint powerhsell commandline...
Make sure you call CopyTo(url) method on SPFile, not on SPListItem.
for example:
ItemUpdated(SPItemEventProperties properties)
{
//...
string url = properties.Web.Site.Url + "/" + properties.Web.Name + "Lists/ListName/" + properties.ListItem.File.Name;
//properties.ListItem.File.MoveTo(url);
properties.ListItem.File.CopyTo(url);
//...
}
private void CopyAttachmentsToList(SPListItem srcItem, SPListItem tgtItem)
{
try
{
//get source item attachments from the folder
SPFolder srcAttachmentsFolder =
srcItem.Web.Folders["Lists"].SubFolders[srcItem.ParentList.Title].SubFolders["Attachments"].SubFolders[srcItem.ID.ToString()];
//Add items to the target item
foreach (SPFile file in srcAttachmentsFolder.Files)
{
byte[] binFile = file.OpenBinary();
tgtItem.Update();
tgtItem.Attachments.AddNow(file.Name, binFile);
tgtItem.Update();
}
}
catch
{
//exception message goes here
}
finally
{
srcItem.Web.Dispose();
}
}
Don't forget to add this line, tgtItem.Update();, else you will get an err.
So, the lists have the exact same or similar columns? Either way, you could create a simple workflow that runs automatically when an item is created in "List A". Since the workflow in question is relatively simple, I'd recommend using SharePoint Designer (which is free) to create it, since you can easily match up the columns from the two lists. The walk through below should be able to help you get started.
Create a Workflow - SharePoint Designer
I had the same problem.
After experimenting a bit instead of
targetItem[f.InternalName] = sourceItem[f.InternalName];
I used:
targetItem[childField.Title] = sourceItem[parentField.Title];
How to copy field and save versions:
public static SPListItem CopyItem(SPListItem sourceItem, SPList destinationList)
{
SPListItem targetItem = destinationList.AddItem();
//loop over the soureitem, restore it
for (int i = sourceItem.Versions.Count - 1; i >= 0; i--)
{
//set the values into the archive
foreach (SPField sourceField in sourceItem.Fields)
{
SPListItemVersion version = sourceItem.Versions[i];
if ((!sourceField.ReadOnlyField) && (sourceField.InternalName != "Attachments"))
{
SetFields(targetItem, sourceField, version);
}
}
//update the archive item and
//loop over the the next version
targetItem.Update();
}
foreach (string fileName in sourceItem.Attachments)
{
SPFile file = sourceItem.ParentList.ParentWeb.GetFile(sourceItem.Attachments.UrlPrefix + fileName);
targetItem.Attachments.Add(fileName, file.OpenBinary());
}
targetItem.SystemUpdate();
return targetItem;
}
private static bool SetFields(SPListItem targetItem, SPField sourceField, SPListItemVersion version)
{
try
{
targetItem[sourceField.InternalName] = version.ListItem[sourceField.InternalName];
return true;
}
catch (System.ArgumentException)//field not filled
{
return false;
}
catch (SPException)//field not filled
{
return false;
}
}
Copy List Items from one SharePoint List or library to Another SharePoint list or library using c# server side code
//Itecollection is a collection of data from source list
public void CopyItemsFromOneListToAnotherList(SPListItemCollection itemCollection)
{
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
//Get destination list/library
//destListName - Destination list/library name
SPList destList = web.Lists.TryGetList(destListName);
foreach (SPListItem sourceItem in itemCollection)
{
//Add new Item to list
SPListItem destItem = destList.Items.Add();
foreach (SPField field in sourceItem.Fields)
{
if (!field.ReadOnlyField && !field.Hidden && field.InternalName != "Attachments")
{
if (destItem.Fields.ContainsField(field.InternalName))
{
//Copy item to destination library
destItem[field.InternalName] = sourceItem[field.InternalName];
}
}
}
//Update item in destination library or list
destItem.Update();
Console.WriteLine("Copied " + sourceItem["ID"] + "to destination list/library");
}
}
}
}

Categories

Resources