Compare Sharepoint List with C# - c#

I am trying to compare two Sharepoint lists. I am using a C# program to add, update, and delete items, based on its ID. If the ID doesnt exist in List1, when the program is ran, I want to delete the IDs from List2. I was wondering how can I delete those items without specifying a specific number in the GetItemById function? Like in this example
using(ClientContext context = new ClientContext(siteUrl)) {
//Retrieve list items from list 1 here code here
using(ClientContext target = new ClientContext(siteUrl2)) {
foreach(ListItem oListItem2 in collListItem2) {
int exists = 0;
foreach(ListItem oListItem in collListItem) {
if (oListItem2["ID"] == oListItem["ID"]) {
exists++;
}
}
if (exists == 0) {
ListItem DeleteItem = list2.GetItemById();
DeleteItem.DeleteObject();
target.ExecuteQuery();
}
return;
}
}
}

To delete the items from the second list not in the first, just get all of the items from the first list, and filter the items in the second list based on those ids. Note you can use a hash based lookup to greatly improve performance over a linear search:
var idsFromFirstList = new HashSet<int>(
collListItem.AsEnumerable()
.Select(item => item.Id));
var itemsToDelete = collListItem2.AsEnumerable()
.Where(item => !idsFromFirstList.Contains(item.Id);
foreach(var item in itemsToDelete)
item.DeleteObject();
target.ExecuteQuery();
Note that you can basically do the exact opposite to find the items to add (create a hashset of the IDs of the items in the target into a set, find all items in the first no tin there, and then add all of those items).
To find items that match you can use a Dictionary<int, ListItem> by putting either set of items into a dictionary, with the ID as the key, and going through the other set, finding the matches. If you are going to do that, you can re-use that dictionary to check for one of the other two conditions as well, to save you one data structure:
var firstSiteItemLookup = collListItem.AsEnumerable()
.ToDictionary(item => item.Id, item => item);
foreach(var item in collListItem2)
{
ListItem match;
if(firstSiteItemLookup.TryGetValue(item.Id, out match))
UpdateItemToMatch(item, match);
else
item.DeleteObject();
}
target.ExecuteQuery();

Can you use the same code to check if an ID in collListItem has been modified then update the same ID in collListItem2? #Servy

Related

Retrieve the previous indexed record in a list, if exists

I am using .NET 3.5 . My requirement is to traverse through a list of objects ordered descending by date, find a match for a particular record, capture that object and then, IF a record exists on a date prior to that, which means the captured object's index minus one (if exists), capture this object too, which means my output list can have either one record or two records depending on whether there was a previous dated record or not. Is there a clean way of achieving this ?
I tried capturing index of matched record and going for previous index by adding -1 to index >> Risk of index out of bounds exception if the previous element does not exist.
How can I avoid the index out of bound exception, yet check for the existence of previous element , if exists ? I am sure there is a much cleaner way of doing it rather than the way I am trying. So I am reaching out to you to advice if there is a nicer way of doing this....
Any advise is highly appreciated. Thank you
Take a look at the below. Object is just a datetimeoffset, but should illustrate LINQ query. You are looking for .Top(2) (this could be more complicated if you need this grouped by something):
LinqPad sample below, but should be easily pasted into console app.
void Main()
{
var threeItems = new List<DateTimeOffset>(new[] { DateTimeOffset.Now, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(-2) });
var twoItems = new List<DateTimeOffset>(new[] { DateTimeOffset.Now, DateTimeOffset.Now.AddDays(-1) });
var oneItem = new List<DateTimeOffset>(new[] { DateTimeOffset.Now });
ShowItems(GetItems(threeItems));
ShowItems(GetItems(twoItems));
ShowItems(GetItems(oneItem));
}
IEnumerable<DateTimeOffset> GetItems(List<DateTimeOffset> items)
{
return items
.OrderByDescending(i => i)
.Select(i => i)
.Take(2);
}
void ShowItems(IEnumerable<DateTimeOffset> items)
{
Console.WriteLine("List of Items:");
foreach (var item in items)
{
Console.WriteLine(item);
}
}
I think what you are looking for will require using the List.IndexOf to find the index of the matching item, then retreive the previous item if there is a datetime before the date you have searched for. My example here uses an object called listObject which contains a datetime as well as other properties;
DateTime searchDate = DateTime.Parse("26/01/2019");
var orderedList = listObjects.OrderBy(x => x.DateProperty).ToList();
listObject matchingItem = orderedList.First(x => x.DateProperty.Date == searchDate.Date); //gets the first matching date
listObject previousMatching = orderedList.Any(x => x.DateProperty.Date < searchDate.Date) ? orderedList[orderedList.IndexOf(matchingItem) - 1] : null; //returns previous if existing, else returns null

Update list with a item modification with linq

Suppose i have a list
List<ABCClass> lstABC; // it has some data
now need to modify a item from list like
var item = lstABC.Where(a=>a.index == 1).FirstOrDefault();
if(item != null)
item.Number = 5;
now what is the most efficient way to assign this item in lstABC again because i need updated lstABC without creating new list and assign to it.
As JohnB pointed out in the comments, if ABCClass really is a class (and not a struct), then item refers to the object in the list, and any change to it will be visible next time you run through the list.
However, if ABCClass really is a struct, then item is just a copy of the object in the list, and changes to it will not be reflected in the list.
if that case (which is unlikely), you'd need to do something list this:
List<int> lstABC; // it has some data
var idx = lstABC.FindIndex(a => a.index == 1);
if (idx != -1)
lstABC[idx].Number = 5;

Sort dynamic dropdown list alphabetically

I have a dropdown with three strings horizontally for each item. How can I alphabetically sort first by string 1 from each item, then by string 2 from each item?
Here's my snippet:
foreach (var item in list)
{
if(typeof(T).Equals(typeof(Machine)))
{
Machine machine = (item as Machine);
string title = machine.MachineName + " - " + machine.Serial + " - " + machine.MachineOwnership;
alert.AddAction(UIAlertAction.Create(title, UIAlertActionStyle.Default, action => {
button.SetTitle(title, UIControlState.Normal);
}));
}
else if(typeof(T).Equals(typeof(Person)))
{
alert.AddAction(UIAlertAction.Create((item as Person).Name, UIAlertActionStyle.Default, action => {
button.SetTitle((item as Person).Name, UIControlState.Normal);
}));
}
}
Where list contains objects of type Machine which has the following properties:
MachineName, Serial and MachineOwnshership (all strings).
So I want to do something like OrderBy(MachineName).ThenBy(Serial) but not sure how to do so correctly when I'm first checking to see what the list type is and then populating the dropdown list per item.
My dropdown list looks something like this if anyone needs clarification:
-------------------------------------------------
MachineNameStartsWithA - 01234 - OwnerStartsWithA
--------------------------------------------------
MachineNameStartsWithB - 012345 - OwnerStartsWithB
---------------------------------------------------
etc.... where it's a long list of items where the strings are separated by "-" like it's shown in the code.
Also, for what it's worth, this is currently inside a Xamarin app.
I think something like this should work. Sorting by a Tuple should be lexicographic:
list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial))
The OfType call should also remove the need for the typeof check and cast - the result should be an iterable of Machines.
Per the comment regarding multiple object types:
Grouping like types together
This would be my default preference from a UI/UX perspective unless it really makes sense to mix Machines and Persons together. Depending on the length of the list it may be preferable to split on the element type first as list.OfType will enumerate the entire list each time.
foreach(var item in list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial)))
{
// append item
}
foreach(var item in list.OfType<Person>().OrderBy(x => x.Name))
{
// append item
}
Interleaving various types
private Tuple<string,string> Projection(BaseClass x)
{
Machine item = x as Machine;
if(item != null)
{
return new Tuple<string,string>(item.MachineName,item.Serial);
}
Person item = x as Person;
if(item != null)
{
return new Tuple<string,string>(item.Name,"");
}
}
foreach(var item in list.OrderBy(Projection))
{
// check type of item and append as appropriate
}

Linq OrderBy not sorting correctly 100% of the time

I'm using the Linq OrderBy() function to sort a generic list of Sitecore items by display name, then build a string of pipe-delimited guids, which is then inserted into a Sitecore field. The display name is a model number of a product, generally around 10 digits. At first it seemed like this worked 100% of the time, but the client found a problem with it...
This is one example that we have found so far. The code somehow thinks IC-30R-LH comes after IC-30RID-LH, but the opposite should be true.
I put this into an online alphabetizer like this one and it was able to get it right...
I did try adding StringComparer.InvariantCultureIgnoreCase as a second parameter to the OrderBy() but it did not help.
Here's the code... Let me know if you have any ideas. Note that I am not running this OrderBy() call inside of a loop, at any scope.
private string GetAlphabetizedGuidString(Item i, Field f)
{
List<Item> items = new List<Item>();
StringBuilder scGuidBuilder = new StringBuilder();
if (i != null && f != null)
{
foreach (ID guid in ((MultilistField)f).TargetIDs)
{
Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
if (target != null && !string.IsNullOrEmpty(target.DisplayName)) items.Add(target);
}
// Sort it by item name.
items = items.OrderBy(o => o.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
// Build a string of pipe-delimited guids.
foreach (Item item in items)
{
scGuidBuilder.Append(item.ID);
scGuidBuilder.Append("|");
}
// Return string which is a list of guids.
return scGuidBuilder.ToString().TrimEnd('|');
}
return string.Empty;
}
I was able to reproduce your problem with the following code:
var strings = new string[] { "IC-30RID-LH", "IC-30RID-RH", "IC-30R-LH", "IC-30R-RH"};
var sorted = strings.OrderBy(s => s);
I was also able to get the desired sort order by adding a comparer to the sort.
var sorted = strings.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
That forces a character-by-character (technically byte-by-byte) comparison of the two strings, which puts the '-' (45) before the 'I' (73).

Group, Sort, and then Merge to a Observable Collection?

I have a List of Items that have a "DisplayOrder" property. This can either have NULL or int value. If it has int value, it has priority and should be in the 1st group of the Observable Collection. Items in the 1st group are also sorted by DisplayOrder.
If it is NULL, then it belongs to the 2nd group, sorted alphabetically.
The 1st group and 2nd group are then combined for a Main Items Collection Observable Collection which I bind to a ListView.
This is my current code though I am worried if there is a much optimal way of doing it.
var MainItemCollection = new ObservableCollection<MainItemViewModel>();
var ItemsWithProperty = new ObservableCollection<MainItemViewModel>();
var ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>();
foreach (var item in DataObject.MainItems)
{
if (item.DisplayOrder == null)
ItemsWithNullProperty.Add(new MainItemViewModel(item));
else
ItemsWithProperty.Add(new MainItemViewModel(item));
}
ItemsWithProperty = new ObservableCollection<MainItemViewModel>(ItemsWithProperty.OrderBy(c => c.DisplayOrder));
ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>(ItemsWithNullProperty.OrderBy(c => c.Title));
//Add those with priorities first sorted by DisplayOrder 1,2,3,4
foreach (var c in ItemsWithProperty)
{
MainItemCollection.Add(c);
}
//Add those without priority sorted Alphabetically
foreach (var c in ItemsWithNullProperty)
{
MainItemCollection.Add(c);
}
Thank you!
Get the items with DisplayOrder=null & order them by Title:
ItemsWithNullProperty=DataObject.MainItems.Where(x=>x.DisplayOrder==null).OrderBy(o=>o.Title).ToList();
Get the items with DisplayOrder(all items except the above query) & order them by DisplayOrder:
ItemsWithProperty= DataObject.MainItems.Except(ItemsWithNullProperty).OrderBy(o=>o.DisplayOrder).ToList();
Fill the data in MainCollection:
var allItems = MainItemCollection.Concat(ItemsWithProperty)
.Concat(ItemsWithNullProperty)
.ToList();
When doing things like this, you don't need all those intermediate ObservableCollections - you can use the appropriate data structures like array, list, dictionary, hash set etc. or Linq queries. In this particular case, the whole procedure can be reduced to something like this
var MainItemCollection = new ObservableCollection<MainItemViewModel>(DataObject.MainItems
.OrderBy(item => item.DisplayOrder ?? int.MaxValue)
.ThenBy(item => item.DisplayOrder == null ? item.Title : string.Empty)
);
I feel that this is a pretty common scenario.
There is a sorted ObservableCollection bound to some XAML UI, and once more data is available UI needs to be updated without full refresh.
Whenever new ObservableCollection is created like in suggestions above, all items will be rebound and therefore UI fully updated.
I'm surprised that there are no library methods to achieve this. Here is the solution I've came up with. Hope someone might find it useful.
public static class ObservableCollectionExtensions
{
public static void MergeSortedListIntoSortedObservableCollection<T>(this ObservableCollection<T> destination, IList<T> list, Func<T, T, int> compareFunc)
{
int dest_index = 0;
int list_index = 0;
while (list_index < list.Count)
{
if (dest_index >= destination.Count)
{
destination.Add(list[list_index]);
list_index++;
dest_index++;
}
else
{
if (compareFunc(destination[dest_index], list[list_index]) > 0)
{
destination.Insert(dest_index, list[list_index]);
list_index++;
dest_index++;
}
else
{
dest_index++;
}
}
}
}
}

Categories