I am trying to create a jumplist for a LongListSelector, I used the AlphaKeyGroup example from MS as a basis.
Once I have created my list I then want to serialize it and store it until the app is next opened.
The problem I have is that the Key is null when I deserialize the ObservableCollection.
I have something like this on my Main Page
public ObservableCollection<AlphaKeyGroup<myClass>> myList { get; set; }
void process()
{
myList = AlphaKeyGroup<myClass>.CreateGroups(System.Threading.Thread.CurrentThread.CurrentUICulture);
getItemsAndAddtoList();
string serializedata = Serialize(myList);
}
void getItemsAndAddtoList()
{
List<myClass> templist=new List<myClass>();
templist.Add(new myClass("Alpha","A in the jumplist"));
templist.Add(new myClass("Beta", "B in the jumplist"));
templist.Add(new myClass("Cappa", "C in the Jumplist"));
addToList(templist.ToArray());
string data = Serialize(myList);
}
void addToList(myClass[] items)
{
if (this.mainList.Dispatcher.CheckAccess())
{
SortedLocaleGrouping slg = new SortedLocaleGrouping(System.Threading.Thread.CurrentThread.CurrentUICulture);
foreach (myClass item in items)
{
int index = 0;
if (slg.SupportsPhonetics)
{
}
else
{
index = slg.GetGroupIndex(item.description);
}
if (index >= 0 && index < myList.Count)
{
myList[index].Add(item);
}
}
}
else
{
this.mainList.Dispatcher.BeginInvoke(new Action<myClass[]>(addToList), new object[] { items });
}
}
public static string Serialize(object obj)
{
using (var sw = new StringWriter())
{
var serializer = new XmlSerializer(obj.GetType());
serializer.Serialize(sw, obj);
return sw.ToString();
}
}
If I break on return sw.ToString(); I can see something like this
<ArrayOfMyClass>
<myClass>
<description>Alpha</description>
<data>A in the jumplist</data>
</myClass>
</ArrayOfMyClass>
As you can see the key from the AlphaKeyGroup class is missing, if I then deserialise this back and set it as the LongListSelector ItemSource I get a Jumplist with No text values, just empty boxes.
public class myClass
{
public string description { get; set; }
public string data { get; set; }
public myClass()
{
}
public myClass(string Desc, string Data)
{
description = Desc;
data = Data;
}
}
public class AlphaKeyGroup<T> : ObservableCollection<T>
{
public delegate string GetKeyDelegate(T item);
public string Key { get; set; }
public AlphaKeyGroup(string key)
{
Key = key;
}
public AlphaKeyGroup()
{
}
public static ObservableCollection<AlphaKeyGroup<T>> CreateGroups(CultureInfo ci)
{
SortedLocaleGrouping slg = new SortedLocaleGrouping(ci);
ObservableCollection<AlphaKeyGroup<T>> list = new ObservableCollection<AlphaKeyGroup<T>>();
foreach (string key in slg.GroupDisplayNames)
{
list.Add(new AlphaKeyGroup<T>(key));
}
return list;
}
}
I am sure I am missing something simple, but I couldn't find a solution, I think it is to do with the Parameterless constructor, but I am not sure, I am quite new to C#.
Related
I have a dictionary where values are stored in the following format -
userID, empDetails
For example,
1234, 'empName,jobDesc,CardNumber,Type'
I have to compare this information with another set of information such that -
If entered userId is present in the above dictionary, then remove this record from the dictionary.
If entered CardNumber is present (here userId is not known) in the above dictionary, then remove this record from the dictionary.
The first condition is simple and can be done by
dictionary.Remove(key)
But I am confused as to how would I implement the second condition. I want something like
if(CardNumber.PresentinAboveDictionary)
then
Remove that record
I know we can compare a partial string in a key like this, but I want to remove the record.
Check if any part of a hashtable value contains certain string c#
Assuming the employment details in your dictionary are a string in the specified format you would need to:
Search the values within the dictionary
Parse/Split the values to get the card numbers
Check the card numbers to see if they match the card number you are checking
Return the key value pair when a match occurs
Remove the entry for the key in the returned key value pair
Example code for the solution:
var dictionary = new Dictionary<int, string>() { { 1, "empName,jobDesc,124124134,Type" } };
var cardNumber = 124124134;
var entry = dictionary.FirstOrDefault(x => DoEmploymentDetailsContainCardNumber(x.Value, cardNumber));
if (!entry.Equals(default(KeyValuePair<int, string>)))
{
dictionary.Remove(entry.Key);
}
Method that checks if card number is present in employment details:
private static bool DoEmploymentDetailsContainCardNumber(string empDetails, int cardNumber)
{
var splitEmpDetails = empDetails.Split(',');
var empDetailsCardNumber = splitEmpDetails[2];
return empDetailsCardNumber == cardNumber.ToString();
}
Instead of Dictionary you can use a strongly typed List
Use the Linq builtin Remove method
Use Parallel.ForEach, iterate the list and remove the item (beware, takes more time)
pseudo code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Collections;
namespace ConsoleApp4
{
public class Employee
{
public Employee(int userID, string empDetails)
{
string[] props = empDetails.Split(new char[] { ',' }, StringSplitOptions.None);
this.userID = userID;
this.empName = props[0];
this.jobDesc = props[1];
this.CardNumber = props[2];
this.Type = props[3];
}
public int userID { get; set; }
public string empName { get; set; }
public string jobDesc { get; set; }
public string CardNumber { get; set; }
public string Type { get; set; }
}
public class MyCustomList : List<Employee>
{
public void Add(int userID, string empDetails)
{
this.Add(new Employee(userID, empDetails));
}
public bool Remove(string CardNumber)
{
bool found = false ;
Parallel.ForEach(this,
(i, state) =>
{
if (i.CardNumber == CardNumber)
{
this.Remove(i);
state.Break();
}
});
return found;
}
public bool RemoveV2(string CardNumber)
{
bool found = false;
if (this.Any(x => x.CardNumber == CardNumber))
{
this.Remove(this.Where(x => x.CardNumber == CardNumber).First());
found = true;
}
return found;
}
}
class Program
{
static void Main(string[] args)
{
var dict = new MyCustomList();//userID, empDetails list
dict.Add(12341, "empName1,jobDesc,CardNumber1,Type");
dict.Add(12342, "empName2,jobDesc,CardNumber2,Type");
dict.Add(12343, "empName3,jobDesc,CardNumber3,Type");
dict.Add(12344, "empName4,jobDesc,CardNumber4,Type");
dict.Add(12345, "empName5,jobDesc,CardNumber5,Type");
dict.Add(12346, "empName6,jobDesc,CardNumber6,Type");
dict.Add(12347, "empName7,jobDesc,CardNumber7,Type");
dict.Add(12348, "empName8,jobDesc,CardNumber8,Type");
//remove CardNumber5
dict.Remove("CardNumber5");
Console.Write(dict);
}
}
}
you can follow the simple approach to remove the key by using a loop here.
Here I am assuming that there is no key with a value of -1 in the dictionary.
int keyToRemove = -1;
foreach (var entry in dictionary)
{
if (entry.Value.Contains(CardNumber))
{
keyToRemove = entry.Key;
break;
}
}
if (keyToRemove != -1)
{
dictionary.Remove(keyToRemove);
}
This is possibly overkill and is not optimised for reading the full dataset repeatedly but it is considerably faster than the accepted solution. I put together a test of the solution below which did the following:
Generated 1,000,000 data rows with unique IDs and card numbers (the solution would also work if the card numbers were not unique)
Randomly removed 100,000 data items by ID and 100,000 data items by card number
Generated a list of the remaining data items
The process took around 75 seconds.
I then tried to repeat steps 1) and 2) using the accepted answer - after around 10 minutes it's about 7% of the way through removing data items. Therefore I think the solution below is around 2 orders of magnitude faster for this type of operation.
There are probably better doubley linked list implementations out there but I am not too familiar with any of them.
namespace Question
{
public class EmployeeCollection
{
private readonly Dictionary<int, ListNode<EmployeeDetails>> _idDictionary = new();
private readonly Dictionary<string, Dictionary<int, EmployeeDetails>> _cardNumberDictionary = new();
private readonly LinkedList<EmployeeDetails> _list = new();
public void AddEmployee(EmployeeDetails details)
{
var node = new ListNode<EmployeeDetails>(details);
_list.AddToStart(node);
_idDictionary.Add(details.Id, node);
if(!_cardNumberDictionary.ContainsKey(details.CardNumber))
{
_cardNumberDictionary.Add(details.CardNumber, new Dictionary<int, EmployeeDetails>());
}
_cardNumberDictionary[details.CardNumber].Add(details.Id, details);
}
public void RemoveById(int id)
{
if (_idDictionary.TryGetValue(id, out var node))
{
_idDictionary.Remove(id);
_list.Remove(node);
var list = _cardNumberDictionary[node.Value.CardNumber];
list.Remove(id);
if(list.Count == 0)
{
_cardNumberDictionary.Remove(node.Value.CardNumber);
}
}
}
public void RemoveByCardNumber(string cardNumber)
{
if (_cardNumberDictionary.TryGetValue(cardNumber, out var employees))
{
_cardNumberDictionary.Remove(cardNumber);
foreach (var employee in employees)
{
if (_idDictionary.TryGetValue(employee.Key, out var node))
{
_list.Remove(node);
}
}
}
}
public IEnumerable<EmployeeDetails> Employees => _list.GetAllValues();
public EmployeeDetails? GetById(int id)
{
if(_idDictionary.ContainsKey(id))
{
return _idDictionary[id].Value;
}
return null;
}
}
public class EmployeeDetails
{
public int Id { get; init; }
public string Name { get; init; }
public string JobDescription { get; init; }
public string CardNumber { get; init; }
public string Type { get; init; }
public static EmployeeDetails FromData(int id, string details)
{
var parts = details.Split(',');
return new EmployeeDetails
{
Id = id,
Name = parts[0],
JobDescription = parts[1],
CardNumber = parts[2],
Type = parts[3],
};
}
}
public class LinkedList<T>
{
public int Count { get; private set; }
private ListNode<T>? Start { get; set; }
private ListNode<T>? End { get; set; }
public bool IsEmpty => Count == 0;
public void AddToStart(ListNode<T> node)
{
ArgumentNullException.ThrowIfNull(nameof(node));
node.Next = null;
node.Previous = null;
if (IsEmpty)
{
Start = End = node;
}
else
{
Start!.Previous = node;
node.Next = Start;
Start = node;
}
Count++;
}
public void Remove(ListNode<T> node)
{
if (node != Start)
{
node.Previous!.Next = node.Next;
}
else
{
Start = node.Next;
}
if (node != End)
{
node.Next!.Previous = node.Previous;
}
else
{
End = node.Previous;
}
Count--;
}
public IEnumerable<T> GetAllValues()
{
var counter = Start;
while (counter != null)
{
yield return counter.Value;
counter = counter.Next;
}
}
}
public class ListNode<T>
{
public T Value { get; }
public ListNode<T>? Previous { get; set; }
public ListNode<T>? Next { get; set; }
public ListNode(T value)
{
Value = value;
}
}
}
you can do something like this.
var recordsToRemove = dictionary.Where(x => x.Value.Contains("what you are looking for"))
.ToList();
if (recordsToRemove.Any())
{
foreach (var record in recordsToRemove)
{
dictionary.Remove(record.Key);
}
}
I have two problem. I need to serialize data to csv and xml but its turn out to be problematic for me.
As xml I desire to get something like:
<sentence>
<word>example1</word>
<word>example2</word>
<word>example3</word>
</sentence>
<sentence>
<word>example1</word>
<word>example2</word>
<word>example3</word>
</sentence>
My data its SentencedModel which contain inside collection of WordsModel. So it like: List<ICollection<string>>. Every position (sentence) in list have collection of string (words).
Class look like:
[Serializable]
public class WordsModel : IEnumerable<string>
{
[XmlRoot("Word")]
public ICollection<string> Words { get; set;}
public IEnumerator<string> GetEnumerator()
{
return this.Words.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Words.GetEnumerator();
}
}
[Serializable]
public class SentencedModel : IEnumerable<WordsModel>
{
[XmlArray("Sentence"), XmlArrayItem(typeof(WordsModel), ElementName = "Words")]
public ICollection<WordsModel> Sentences { get; set; }
public SentencedModel()
{
this.Sentences = new List<WordsModel>();
}
public void Add(WordsModel words)
{
this.Sentences?.Add(words);
}
public IEnumerator<WordsModel> GetEnumerator()
{
return this.Sentences.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Sentences.GetEnumerator();
}
}
My class which is Repositories for that:
public class WordsSeperapedBySentence
{
public SentencedModel WordsSeperatedBySentence { get; }
public WordsSeperapedBySentence()
{
this.WordsSeperatedBySentence = new SentencedModel();
}
public bool AddSentence(ICollection<string> words)
{
if (words == null) return false;
WordsModel wordsModel = new WordsModel();
wordsModel.Words = words;
this.WordsSeperatedBySentence.Add(wordsModel);
return true;
}
}
Here is my serializer class:
public class SerializeData
{
public string SerializeToXml(SentencedModel data)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(SentencedModel));
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, data);
return textWriter.ToString();
}
}
public ToCsv(WordsSeperapedBySentence data)
{
//??
}
}
But after using
List<string> example1 = new List<string>();
example1.Add("Chris");
example1.Add("call");
example1.Add("Anna");
List<string> example2 = new List<string>();
example2.Add("Somebody");
example2.Add("call");
example2.Add("Wolf");
WordsModel words1 = new WordsModel();
WordsModel words2 = new WordsModel();
words1.Words = example1;
words2.Words = example2;
SentencedModel sentenced = new SentencedModel();
sentenced.Add(words1);
sentenced.Add(words2);
SerializeData serialize = new SerializeData();
var stringAsResult = serialize.SerializeToXml(sentenced);
Console.WriteLine(stringAsResult);
I got errors. Also I do not have idea how to storage them to CSV.
Could you help me?
Thank you in advance.
In order to save your data as CSV, you can use the following method which provides this output:
Chris,call,Anna
Somebody,call,Wolf
Each line is a sentence then all the words are separated by commas.
public string ToCsv(SentencedModel data)
{
var csvLines = data.Select(x => String.Join(",", x));
var csv = String.Join(Environment.NewLine, csvLines);
return csv;
}
I am still missing the XML part, if I do, I will edit the answer.
At least you have a part of it.
Edit Please find below the ToCsv with the fields being escaped based on the comments below.
public string ToCsv(SentencedModel data)
{
var csvLines = data.Sentences.Select(x => String.Join(",", x.Words.Select(w => EscapeForCsv(w))));
var csv = String.Join(Environment.NewLine, csvLines);
return csv;
}
private string EscapeForCsv(string input)
{
return String.Format("\"{0}\"", input.Replace("\"", "\"\"\""));
}
First: If you want to tokenize a text - I recommend:
use an array and not a list. For example: string[][]. The reason: List will locate 10%-20% more memory. You can convert a List to Array by .ToArray() (e.g. example1.ToArray) or use the C# 6.0 syntax:
string[][] sentence = new [] { {"Chris","called","Anna"}, {"Somebody","called","Wolf"} };
If possible: use the primitive datatypes - classes are to complex and slowdown your textprocessing.
Second: If you want to implement your own serializer try this approce:
public abstract class AbstractSerializer
{
public abstract void Serialize(string[][] model, string path);
}
public class XmlSerializer : AbstractSerializer
{
public override void Serialize(string[][] model, string path)
{
// your stuff
}
}
public class CsvSerializer : AbstractSerializer
{
public string LineSeparator { get; set; } = "\r\n";
public string ValueSeparator { get; set; } = ";";
public override void Serialize(string[][] model, string path)
{
var stb = new System.Text.StringBuilder();
for (int i = 0; i < model.Length; i++)
{
for (int j = 0; j < model[i].Length; j++)
{
// Example output:
// 0;0;Chris
// 0;1;call
// 0;2;Anna
// 1;0;Somebody
// 1;1;call
// 1;2;Wolf
stb.Append(string.Join(ValueSeparator, i, j, model[i][j], LineSeparator));
}
}
}
}
I have written following sample code to save a slightly complex object FamilyTreeFile to XML and restore it back to original form.
public class XmlSerializationTest
{
const string FileName = #"FamilyTree.xml";
public void Run()
{
var rootMember = new Member() { Name = "Johny", Parent = null };
var member1 = new Member() { Name = "Andy", Parent = rootMember };
var member2 = new Member() { Name = "Adam", Parent = rootMember };
var member3 = new Member() { Name = "Andrew", Parent = rootMember };
var member4 = new Member() { Name = "Davis", Parent = member2 };
var member5 = new Member() { Name = "Simon", Parent = member4 };
rootMember.FamilyTree = new GenericCollection();
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree = new GenericCollection();
member2.FamilyTree.Add(member4);
member4.FamilyTree = new GenericCollection();
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Serialize(file);
file = Deserialize();
}
public void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
}
[Serializable]
public class Member : IMember
{
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public IMember Parent { get; set; }
public GenericCollection FamilyTree { get; set; }
public Member()
{
//FamilyTree = new GenericCollection();
}
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
if (reader.Name == "FamilyTree")
{
do
{
reader.Read();
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
}
if (reader.Name == "FamilyTree" && reader.NodeType == System.Xml.XmlNodeType.EndElement)
break;
}
while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
The code sample is generating the following XML file which is exactly as per my needs but i am unable to read it back using ReadXml method.
<?xml version="1.0" encoding="utf-8"?>
<FamilyTreeFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FamilyTree>
<Member Name="Johny">
<FamilyTree>
<Member Name="Andy" />
<Member Name="Adam">
<FamilyTree>
<Member Name="Davis">
<FamilyTree>
<Member Name="Simon" />
</FamilyTree>
</Member>
</FamilyTree>
</Member>
<Member Name="Andrew" />
</FamilyTree>
</Member>
</FamilyTree>
</FamilyTreeFile>
I need help in how can i restore it back efficiently?
ADDED
Upon adding new collection Notes in IMember
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
Implementing this property in Member class
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
I am unable to deserialize Notes information at this line.
var member = (IMember)xmlSerializer.Deserialize(reader);
Isn't there any simple way to deserialize using XmlSerializer or any framework which handles everything itself?
Here is a working version of the GenericCollection.ReadXml method for you:
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember) xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
What you will most propably have missed out on in your version, was the fact that every call to a method of the XmlReader like Read...() or Move...() does advance it's reading position. The inner deserialization of member objects does the same.
Keeping this in mind, it should become clear that you simply cannot always issue a Read() at the beginning of the loop, but only at the very end. This way you can skip it with the continue keyword, in case some other code in the loop body (like Deserialize() in our case) did already advance the XmlReader. Same applies to MoveToContent() at the beginning of your version of the method. What I initially did miss out on too, is the fact that the collection of members can be empty. In that case the deserialization of GenericCollection has to be omitted completely, as (again) not to mess up the reader.
While this does deserialize the object instances and adds them to their respective lists, the references (the Parent field of the Member class in this example) are not reconstructed. Here is where things get tricky: A reference is esentially a memory adress. Being that, there is no point in serializing it's value and deserializing it back again. Because the objects will most propably reside in another memory location now, the deserialized address would be entirely wrong.
There are basically two ways to solve this:
The serialized objects could be constructed in a manner that automatically creates those references, when the objects are constucted or glued together. This way there is simply no serialization and deserialization needed. The drawback is: This is only possible for references that can be obtained in this manner (is the case in the current example)
Every object that can be target of a reference cold be extended by an identifier field, quite similar to a primary key in a database. This identifier (for example a guid) is then to be serialized and deserialized. Every referece field (the Parent field of the Member class in this example) is to be serialized as identifier value of the object it references (could be done by adding a helper field ParentID, which is set automatically by the setter of the Parent filed). When everything is deserialized, these references have to be reconstructed by walking the entire tree of objects. On the plus side, this enables one to reconstruct arbitrary references. But one has to be aware of this adding some real complexity to the code.
First approach could be done by:
Changing this in your Run() function...
var rootMember = new Member() { Name = "Johny"};
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
...change property FamilyTree of class Member to this...
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
... and insert this into class GenericCollection
private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
foreach (var member in this)
{
member.Parent = value;
}
}
}
public void Add(IMember item)
{
item.Parent = Owner;
base.Add(item);
}
The second approach is implemented in the following small console application:
class Program
{
public static string FileName = #"FamilyTree.xml";
static void Main(string[] args)
{
// make some members
var rootMember = new Member() { Name = "Johny" };
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
// construct some arbitrary references between them
member1.Reference = member4;
member3.Reference = member1;
member5.Reference = member2;
// let member 3 have some notes
member3.Notes = new List<Note>();
member3.Notes.Add(new Note() { Text = "note1" });
member3.Notes.Add(new Note() { Text = "note2" });
// add all of the to the family tree
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree.Add(member4);
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Console.WriteLine("--- input ---");
Serialize(file);
PrintTree(file.FamilyTree, 0);
Console.WriteLine();
Console.WriteLine("--- output ---");
file = Deserialize();
file.FamilyTree.RebuildReferences(file.FamilyTree); // this is where the refereces
// are put together again after deserializing the object tree.
PrintTree(file.FamilyTree, 0);
Console.ReadLine();
}
private static void PrintTree(GenericCollection c, int indent)
{
foreach (var member in c)
{
string line = member.Name.PadLeft(indent, ' ');
if (member.Reference != null)
{
line += " (Ref: " + member.Reference.Name + ")";
if (member.Notes != null && member.Notes.Count > 0)
{
line += " (Notes: ";
foreach (var note in member.Notes)
{
line += note.Text + ",";
}
line += ")";
}
}
Console.WriteLine(line);
PrintTree(member.FamilyTree, indent + 4);
}
}
public static void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public static IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
Guid ID { get; set; }
string Name { get; set; }
IMember Reference { get; set; }
Guid ReferenceID { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
void RebuildReferences(GenericCollection in_Root);
}
[Serializable]
public class Member : IMember
{
private GenericCollection _FamilyTree;
private IMember _Reference;
[XmlAttribute]
public Guid ID { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public Guid ReferenceID { get; set; }
[XmlIgnore]
public IMember Reference
{
get { return _Reference; }
set
{
ReferenceID = value.ID;
_Reference = value;
}
}
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
public Member()
{
ID = Guid.NewGuid();
FamilyTree = new GenericCollection();
}
public void RebuildReferences(GenericCollection in_Root)
{
if (!ReferenceID.Equals(Guid.Empty))
Reference = in_Root.FindMember(ReferenceID);
FamilyTree.RebuildReferences(in_Root);
}
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
}
}
public void Add(IMember item)
{
base.Add(item);
}
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
public void RebuildReferences(GenericCollection in_Root)
{
foreach (IMember meber in this)
{
meber.RebuildReferences(in_Root);
}
}
public IMember FindMember(Guid in_ID)
{
IMember FoundMember = null;
foreach (IMember member in this)
{
if (member.ID.Equals(in_ID))
return member;
FoundMember = member.FamilyTree.FindMember(in_ID);
if (FoundMember != null)
return FoundMember;
}
return null;
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
A proof of concept for your addition to the original question is disclosed within this second example.
I am attempting to iterate through existing sounds (isolatedGroup) and checking if each still exists in the list provided by assetsGroup. If not, I want it removed. I am getting 3 errors when I add the portion of code for iterating.
Errors:
Operator '<' cannot be applied to operands of type 'int' and 'method group'
Cannot apply indexing with [] to an expression of type SaveSoundGroup'
.SaveSoundGroup' does not contain a definition for 'RemoveAt' and no extension method 'RemoveAt' accepting a first argument of type .SaveSoundGroup' could be found (are you missing a using directive or an assembly reference?)
The errors occur at
for (int i = 0; i < isolatedGroup.Count; i++)
{
if (!assetsGroup.Contains(isolatedGroup[i]))
{
isolatedGroup.RemoveAt(i);
i--;
}
Full code:
private SoundGroup LoadFromXml(string xmlName)
{
SaveSoundGroup isolatedGroup = new SaveSoundGroup();
SaveSoundGroup assetsGroup;
XmlSerializer serializer = new XmlSerializer(typeof(SaveSoundGroup));
using (Stream fileStream = Application.GetResourceStream(new Uri("Xmls/" + xmlName, UriKind.Relative)).Stream)
{
assetsGroup = (SaveSoundGroup)serializer.Deserialize(fileStream);
}
if (IsolatedStorageFile.GetUserStoreForApplication().FileExists(xmlName))
{
using (Stream fileStream = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(xmlName, FileMode.Open, FileAccess.Read))
{
isolatedGroup = (SaveSoundGroup)serializer.Deserialize(fileStream);
}
foreach (var entry in assetsGroup.Items)
{
if (!isolatedGroup.Items.Contains(entry))
{
isolatedGroup.Items.Add(entry);
}
}
for (int i = 0; i < isolatedGroup.Count; i++)
{
if (!assetsGroup.Contains(isolatedGroup[i]))
{
isolatedGroup.RemoveAt(i);
i--;
}
}
}
else
{
isolatedGroup = assetsGroup;
}
return new SoundGroup(isolatedGroup);
}
SavedSoundGroup code:
public class SoundGroup
{
public string Title { get; set; }
public List<Group<SoundData>> Items { get; set; }
public SoundGroup()
{
Items = new List<Group<SoundData>>();
}
public SoundGroup(SaveSoundGroup data)
{
this.Title = data.Title;
this.Items = new List<Group<SoundData>>();
foreach (var group in data.Items.GroupBy(item => item.Groups))
{
this.Items.Add(new Group<SoundData>(group.Key, group.ToList()));
}
}
}
public class SaveSoundGroup
{
public string Title { get; set; }
public List<SoundData> Items { get; set; }
public SaveSoundGroup()
{
this.Title = string.Empty;
this.Items = new List<SoundData>();
}
public SaveSoundGroup(SoundGroup data)
: this()
{
if (data != null)
{
this.Title = data.Title;
this.Items = new List<SoundData>();
foreach (var group in data.Items)
{
this.Items.AddRange(group);
}
}
}
}
Sample of XML:
<SaveSoundGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Title>cellular os</Title>
<Items>
<SoundData>
<Title>** Outro Lex Ring **</Title>
<FilePath>https://*********/Outro_Lex_Ring.mp3</FilePath>
<Groups>Animals</Groups>
<SavePath>Outro Lex Ring.mp3</SavePath>
<Status>NotDownloaded</Status>
<DownloadProgress>0</DownloadProgress>
</SoundData>
<SoundData>
<Title>Blackberry 3Beeps</Title>
<FilePath>https://*********/Blackberry_3Beeps.mp3</FilePath>
<Groups>Blackberry</Groups>
<SavePath>Blackberry_3Beeps.mp3</SavePath>
<Status>NotDownloaded</Status>
<DownloadProgress>0</DownloadProgress>
</SoundData>
This sort of sounds like you want to do a union operation. So consider to do isolatedGroup.Union(assetsGroup) to get an Enumerable of all soundGroups that are in both lists.
The error you get is probably due to a missing () after Count but that depends on how it is defined in SaveSoundGroup. Same goes for the other errors. It seems SaveSoundGroup should inherit from ICollection or Collection but is not defined that way. That's why a lot of stuff your are using does not work. So please consider to post the code of your SaveSoundGroup class.
UPDATE
Ok, after having your SaveSoundGroup class I would recommend to do it like this:
public class SoundGroup : List<Group<SoundData>>
{
public string Title { get; set; }
public SoundGroup() {}
public SoundGroup(SaveSoundGroup data) : base(data.GroupBy(item => item.Groups).Select(grp => new Group<SoundData>(grp.Key, grp.ToList())))
{
this.Title = data.Title;
}
}
public class SaveSoundGroup : List<SoundData>
{
public string Title { get; set; }
public SaveSoundGroup()
{
this.Title = string.Empty;
}
public SaveSoundGroup(SoundGroup data)
: base()
{
this.Title = data.Title;
foreach(var grp in data)
this.AddRange(grp);
}
}
Stuff like Count and LINQ should work now. So you can do what you want now with isolatedGroup.Union(assetsGroup) as I already wrote.
Found the solution:
for (int i = 0; i < isolatedGroup.Items.Count; i++)
{
if (!assetsGroup.Items.Contains(isolatedGroup.Items[i]))
{
isolatedGroup.Items.RemoveAt(i);
i--;
}
}
I have this LINQ statement that tries to set the 1st element in the collection of string[]. But it doesn't work.
Below is the LINQ statement.
docSpcItem.Where(x => x.DocID == 2146943)
.FirstOrDefault()
.FinishingOptionsDesc[0] = "new value";
public string[] FinishingOptionsDesc
{
get
{
if (this._FinishingOptionsDesc != null)
{
return (string[])this._FinishingOptionsDesc.ToArray(typeof(string));
}
return null;
}
set { this._FinishingOptionsDesc = new ArrayList(value); }
}
What's wrong with my LINQ statement above?
Couple of things.. There are some problems with your get and set. I would just use auto properties like this..
public class DocSpcItem
{
public string[] FinishingOptionsDesc { get; set; }
public int DocID { get; set; }
}
Next for your linq statement, depending on the presence of an item with an id of 2146943 you might be setting a new version of the object rather than the one you intended. This should work..
[TestMethod]
public void Linq()
{
var items = new List<DocSpcItem>();
//2146943
for (var i = 2146930; i <= 2146950; i++)
{
items.Add(new DocSpcItem()
{ DocID = i
, FinishingOptionsDesc = new string[]
{ i.ToString() }
}
);
}
var item = items.FirstOrDefault(i => i.DocID == 2146943);
if (item != null)
{
item.FinishingOptionsDesc = new string[]{"The New Value"};
}
}
and
public class DocSpcItem
{
public string[] FinishingOptionsDesc { get; set; }
public int DocID { get; set; }
}