I'm using Linq/Lambda to write output to an XML file, just in the standard way:
new XElement("Employees",
from emp in empList
select new XElement("Employee",
new XAttribute("ID", emp.ID),
new XElement("FName", emp.FName),
new XElement("LName", emp.LName),
new XElement("DOB", emp.DOB),
new XElement("Sex", emp.Sex)
));
The issue I'm running into in effect is that my emp class contains fields that don't implement the IEnumerable interface, but which themselves also contain fields (imagine, for example, the emp included a "WorkHistory" field, which itself contained a set of fields related to complaints, commendations, etc). These latter fields are optional (and non-repeating) in the XML schema.
Is there any way of checking whether they have been set (i.e., whether they are null or not) given the Linq/Lambda framework? If they are not set, then the equivalent XML node needs to be absent.
Hope that made sense. I'm new to Linq/Lambda stuff, so sorry if it sounds confused.
I wrote a simple driver. In future, follow this guide when posting.
You can do this several ways. Out of curiosity, I tried out linq:
using System;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
var empList = new List<Employee>();
for (int i=0; i<10; i++) {
empList.Add(GenerateTestEmployee(i));
}
var xmlConverter = new XmlConverter();
var employeesNode = new XElement(
"Employees",
empList.Select(emp => xmlConverter.Convert(emp))
);
Console.WriteLine(employeesNode.ToString());
}
private static Employee GenerateTestEmployee(int seed) {
return new Employee() {
ID = Guid.NewGuid(),
FName = seed.ToString(),
LName = "Example",
DOB = DateTime.UtcNow.AddYears(-20).AddYears(-seed),
Sex = seed % 2 == 0 ? "Male" : "Female",
WorkHistory = GenerateTestWorkHistory(seed)
};
}
private static WorkHistory GenerateTestWorkHistory(int seed) {
if (seed % 7 == 0) {
return null;
}
return new WorkHistory() {
Complaints = Enumerable.Repeat("Complaint!", seed % 2).ToList(),
Commendations = Enumerable.Repeat("Commendation!", seed % 3).ToList()
};
}
}
public class Employee {
public Guid ID { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public DateTime DOB { get; set; }
public string Sex { get; set; }
public WorkHistory WorkHistory { get; set; }
}
public class WorkHistory {
public List<string> Complaints { get; set; }
public List<string> Commendations { get; set; }
}
public class XmlConverter {
public XElement Convert(Employee emp) {
var attributes = new List<XAttribute> {
new XAttribute("ID", emp.ID)
};
var elements = new List<XElement> {
new XElement("FName", emp.FName),
new XElement("LName", emp.LName),
new XElement("DOB", emp.DOB),
new XElement("Sex", emp.Sex)
};
var workHistory = Convert(emp.WorkHistory);
if (workHistory != null) {
elements.Add(workHistory);
}
return new XElement("Employee", attributes, elements);
}
private XElement Convert(WorkHistory hist) {
if (hist == null) {
return null;
}
var elements = new List<XElement>();
if (hist.Complaints != null && hist.Complaints.Any()) {
var complaints = new XElement(
"Complaints",
hist.Complaints.Select(comp => new XElement("Complaint", comp))
);
elements.Add(complaints);
}
if (hist.Commendations != null && hist.Commendations.Any()) {
var commendations = new XElement(
"Commendations",
hist.Commendations.Select(comm => new XElement("Commendation",comm))
);
elements.Add(commendations);
}
return elements.Any() ? new XElement("WorkHistory", elements)
: null;
}
}
}
Good luck!
PS: I find this online IDE helpful for testing snippets.
Using method syntax, as opposed to query syntax, you can build Employee element gradually, adding optional child elements only when it should :
new XElement("Employees",
empList.Select(emp => {
var xe = new XElement("Employee",
new XAttribute("ID", emp.ID),
....
));
// do further checks and add optional child elements accordingly
if (emp.WorkHistory != null) xe.Add(new XElement(...));
// return the final result
return xe;
})
)
Related
I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }
Can I perform a select using ternary operator to get an attribute from object inside a list?
Here is my model:
public class Xpto
{
public List<Son> Sons { get; set; }
}
public class Son
{
public string Names { get; set; }
}
And here i would like to get "Name" attribute for each son that i have:
var result = (from a in mylist
select new
{
sonsNames = a.Sons == null : <What should i put here?>
}).ToList<object>();
I've tried Sons.ToString() but it prints an object reference.
I would like to have a string list in "sonsNames" and each name separeted by a ','. Example: sonsName: 'george, john'.
what about this ?
//set up a collection
var xptos = new List<Xpto>()
{ new Xpto()
{ Sons = new List<Son>
{ new Son() { Names = "test1" },
new Son() { Names = "test2" }
}
},
new Xpto()
{
Sons = new List<Son> {
new Son() { Names = "test3" }
}
}};
//select the names
var names = xptos.SelectMany(r => r.Sons).Where(k => k.Names != null)
.Select(r => r.Names + ",") .ToList();
names.ForEach(n => Console.WriteLine(n));
Here's more info on SelectMany()
First, I did check this post but it is in Python, first, and second it appears to be actually making the directories, which I cannot do in this scenario.
Second, these are not directories that exist, nor can I create them.
I have an input in C# like this:
List<string> filePaths = new List<string>();
filePaths.Add(#"ProgramDir\InstallDir\Module1\mod1pack1.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module1\mod1pack2.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module2\mod2pack1.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module1\SubModule1\report1.rpt");
filePaths.Add(#"SystemDir\DependencyDir\dependency1.dll");
filePaths.Add(#"SystemDir\DependencyDir\dependency2.dll");
What I have been trying to do is create an object that represents this structure, such that it could be visualized like this:
-ProgramDir
Installdir
Module1
mod1pack1.exe
mod1pack2.exe
-SubModule1
report1.rpt
Module2
mod2pack1.exe
-SystemDir
-DependencyDir
dependency1.dll
dependency2.dll
What I have tried is various versions of the following, and I could really use some help to figure out where I've got it wrong.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
SetFilePathList();
DTree forest = new DTree();
List<DTreeBranch> branches = new List<DTreeBranch>();
foreach (string path in filePaths)
{
forest.GrowTree(path.Split('\\'), branches);
}
forest.SubBranches.AddRange(branches);
}
private static List<string> filePaths { get; set; }
private static void SetFilePathList()
{
filePaths = new List<string>();
filePaths.Add(#"ProgramDir\InstallDir\Module1\mod1pack1.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module1\mod1pack2.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module2\mod2pack1.exe");
filePaths.Add(#"ProgramDir\InstallDir\Module1\SubModule1\report1.rpt");
filePaths.Add(#"SystemDir\DependencyDir\dependency1.dll");
filePaths.Add(#"SystemDir\DependencyDir\dependency2.dll");
}
}
public class DTree
{
public List<DTreeBranch> SubBranches { get; set; }
public string BranchName { get; set; }
public DTree() { SubBranches = new List<DTreeBranch>(); }
public DTreeBranch AddChildren(string[] childElements, DTreeBranch branch)
{
DTreeBranch childBranch;
foreach (string element in childElements)
{
childBranch = new DTreeBranch();
childBranch.BranchName = element;
branch.SubBranches.Add(childBranch);
var query = from q in childElements
where q != childBranch.BranchName
select q;
AddChildren(query.ToArray<string>(), childBranch);
}
return branch;
}
public void GrowTree(string[] pathElements, List<DTreeBranch> Branches)
{
DTreeBranch result = Branches.Find(delegate(DTreeBranch b)
{
return b.BranchName == pathElements[0];
});
if (result == null)
{
DTreeBranch newRootBranch = new DTreeBranch();
newRootBranch.BranchName = pathElements[0];
Branches.Add(newRootBranch);
GrowTree(pathElements, Branches);
}
else
{
var query = from q in pathElements
where q != result.BranchName
select q;
DTreeBranch childBranch = AddChildren(query.ToArray<string>(), result);
Branches.Add(childBranch);
}
}
}
public class DTreeBranch
{
public List<DTreeBranch> SubBranches { get; set; }
public string BranchName { get; set; }
public DTreeBranch()
{
SubBranches = new List<DTreeBranch>();
}
}
}
The main thing is that the output is only two layers deep. I guess what I'm saying is that the new elements are added to the depth, not the breadth, and I'm at a loss as to how to effectively work through this. I also think that I have way more code than I need.
Thanks in advance.
I'm not sure exactly what our goals are, but a simple recursive parse will do it quite easily. Wrote this up, and hope it helps. You can make it significantly more fancy if you want, with DTrees and sub branches, or separate collections for Files and Directories, etc. I don't really understand what all that code in there is for. If it has something to do with WIX, I'm sorry ;) And you could always use something like this to parse it out into the tree, and then convert that sanely to a different format.
this assumes no duplicate leaf nodes (file names).
if that isn't the case, just add a sanity check like for directories.
The main "Node" class -
public class Node
{
public string Name { get; set; }
public bool IsDirectory { get; set; }
public List<Node> Children = new List<Node>();
internal void AddChildren(string f)
{
var dirs = Path.GetDirectoryName(f);
if (string.IsNullOrEmpty(dirs))
{
// we are adding a file
var file = Path.GetFileName(f);
Children.Add(new Node {Name = file, IsDirectory = false});
}
else
{
// we are adding a directory
var firstDir = dirs.Split(Path.DirectorySeparatorChar)[0];
var childNode = Children.FirstOrDefault(d => d.Name == firstDir);
if (childNode == null)
{
childNode = new Node {Name = firstDir, IsDirectory = true};
Children.Add(childNode);
}
var subPath = f.Substring(firstDir.Length + 1);
childNode.AddChildren(subPath);
}
}
}
Calling it is simple, like this:
var filePaths = new List<string> {
#"ProgramDir\InstallDir\Module1\mod1pack1.exe",
#"ProgramDir\InstallDir\Module1\mod1pack2.exe",
#"ProgramDir\InstallDir\Module2\mod2pack1.exe",
#"ProgramDir\InstallDir\Module1\SubModule1\report1.rpt",
#"SystemDir\DependencyDir\dependency1.dll",
#"SystemDir\DependencyDir\dependency2.dll",
};
var node = new Node { Name = "Root", IsDirectory = true };
foreach (var f in filePaths )
{
node.AddChildren(f);
}
Printing it out (with indent per level, gives me this)
public static void PrintNode(Node node, int indent)
{
if (indent > 0) // don't print out root directory (level 1).
{
var ending = node.IsDirectory ? Path.DirectorySeparatorChar.ToString() : "*";
Console.WriteLine("{0}{1}{2}", new string('\t', indent - 1), node.Name, ending);
}
node.Children.ForEach(n => PrintNode(n, indent + 1));
}
ProgramDir\
InstallDir\
Module1\
mod1pack1.exe*
mod1pack2.exe*
SubModule1\
report1.rpt*
Module2\
mod2pack1.exe*
SystemDir\
DependencyDir\
dependency1.dll*
dependency2.dll*
I got about the same as Andrew:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
public static void Main(String[] args)
{
var filePaths = new List<string> {#"ProgramDir\InstallDir\Module1\mod1pack1.exe", #"ProgramDir\InstallDir\Module1\mod1pack2.exe", #"ProgramDir\InstallDir\Module2\mod2pack1.exe", #"ProgramDir\InstallDir\Module1\SubModule1\report1.rpt", #"SystemDir\DependencyDir\dependency1.dll", #"SystemDir\DependencyDir\dependency2.dll"};
var nodes = Parse(filePaths.ToArray());
foreach (var node in nodes)
Console.Out.WriteLine(node.ToString());
Console.ReadLine();
}
public static IEnumerable<Node> Parse(params String[] paths)
{
var roots = new NodeSet();
foreach (var path in paths)
{
var pathSplit = path.Split('\\');
Node current = null;
foreach (var pathElement in pathSplit)
{
var currentRoots = (current == null) ? roots : current.Children;
if (currentRoots.Contains(pathElement))
current = currentRoots[pathElement];
else
currentRoots.Add(current = new Node(pathElement));
}
}
return roots;
}
public class Node
{
public String Name { get; private set; }
public NodeSet Children { get; private set; }
public Node(String name)
{
if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name");
Name = name;
Children = new NodeSet();
}
public override string ToString() { return ToString(1); }
private String ToString(Int32 indent)
{
var indentStr = Environment.NewLine + new string('\t', indent);
return Name + (Children.Count == 0 ? "" : indentStr + String.Join(indentStr, Children.Select(c => c.ToString(indent + 1)).ToArray()));
}
}
public class NodeSet : KeyedCollection<String, Node> {
protected override string GetKeyForItem(Node item) { return item.Name; }
}
}
}
Let us suppose we have a document to store our client which has fixed and extra fields.
So here goes our sample class for the client:
public class Client
{
public string Name{ get; set; }
public string Address{ get; set; }
public List<ExtraField> ExtraFields{ get; set; } //these fields are extra ones
}
In extra field class we have something like this:
public class ExtraField
{
public string Key{ get; set; }
public string Type { get; set; }
public string Value { get; set; }
}
If I use standard driver's behaviour for serialization I would get smth like this:
{{Name:VName, Address:VAddress, ExtraFields:[{Key:VKey,Type:VType,
Value:VValue},...]}, document2,...,documentn}
While I would like to have something like this:
{{Name:VName, Address:VAddress, VKey:VValue,...}, document2,...,documentn}
This would improve the search performance and is generally the point of document orientation.
How can I customize the serialization to such a way?
Here is the way I solved it (it works fine) and solved the issue.
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
namespace TestDataGeneration {
public class FieldsWrapper : IBsonSerializable
{
public List<DataFieldValue> DataFieldValues { get; set; }
public object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
{
if (nominalType != typeof(FieldsWrapper)) throw new ArgumentException("Cannot deserialize anything but self");
var doc = BsonDocument.ReadFrom(bsonReader);
var list = new List<DataFieldValue>();
foreach (var name in doc.Names)
{
var val = doc[name];
if (val.IsString)
list.Add(new DataFieldValue {LocalIdentifier = name, Values = new List<string> {val.AsString}});
else if (val.IsBsonArray)
{
DataFieldValue df = new DataFieldValue {LocalIdentifier = name};
foreach (var elem in val.AsBsonArray)
{
df.Values.Add(elem.AsString);
}
list.Add(df);
}
}
return new FieldsWrapper {DataFieldValues = list};
}
public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, IBsonSerializationOptions options)
{
if (nominalType != typeof (FieldsWrapper))
throw new ArgumentException("Cannot serialize anything but self");
bsonWriter.WriteStartDocument();
foreach (var dataFieldValue in DataFieldValues)
{
bsonWriter.WriteName(dataFieldValue.LocalIdentifier);
if (dataFieldValue.Values.Count != 1)
{
var list = new string[dataFieldValue.Values.Count];
for (int i = 0; i < dataFieldValue.Values.Count; i++)
list[i] = dataFieldValue.Values[i];
BsonSerializer.Serialize(bsonWriter, list);
}
else
{
BsonSerializer.Serialize(bsonWriter, dataFieldValue.Values[0]);
}
}
bsonWriter.WriteEndDocument();
}
} }
Essentially you just need to implement two methods yourself. First one to serialize an object as you want and second to deserialize an object from db to your Client class back:
1 Seialize client class:
public static BsonValue ToBson(Client client)
{
if (client == null)
return null;
var doc = new BsonDocument();
doc["Name"] = client.Name;
doc["Address"] = client.Address;
foreach (var f in client.ExtraFields)
{
var fieldValue = new BsonDocument();
fieldValue["Type"] = f.Type;
fieldValue["Value"] = f.Value;
doc[f.Key] = fieldValue;
}
return doc;
}
2 Deserialize client object:
public static Client FromBson(BsonValue bson)
{
if (bson == null || !bson.IsBsonDocument)
return null;
var doc = bson.AsBsonDocument;
var client = new Client
{
ExtraFields = new List<ExtraField>(),
Address = doc["Address"].AsString,
Name = doc["Name"].AsString
};
foreach (var name in doc.Names)
{
var val = doc[name];
if (val is BsonDocument)
{
var fieldDoc = val as BsonDocument;
var field = new ExtraField
{
Key = name,
Value = fieldDoc["Value"].AsString,
Type = fieldDoc["Type"].AsString
};
client.ExtraFields.Add(field);
}
}
return client;
}
3 Complete test example:
I've added above two method to your client class.
var server = MongoServer.Create("mongodb://localhost:27020");
var database = server.GetDatabase("SO");
var clients = database.GetCollection<Type>("clients");
var client = new Client() {Id = ObjectId.GenerateNewId().ToString()};
client.Name = "Andrew";
client.Address = "Address";
client.ExtraFields = new List<ExtraField>();
client.ExtraFields.Add(new ExtraField()
{
Key = "key1",
Type = "type1",
Value = "value1"
});
client.ExtraFields.Add(new ExtraField()
{
Key = "key2",
Type = "type2",
Value = "value2"
});
//When inseting/saving use ToBson to serialize client
clients.Insert(Client.ToBson(client));
//When reading back from the database use FromBson method:
var fromDb = Client.FromBson(clients.FindOneAs<BsonDocument>());
4 Data structure in a database:
{
"_id" : ObjectId("4e3a66679c66673e9c1da660"),
"Name" : "Andrew",
"Address" : "Address",
"key1" : {
"Type" : "type1",
"Value" : "value1"
},
"key2" : {
"Type" : "type2",
"Value" : "value2"
}
}
BTW: Take a look into serialization tutorial as well.
I am writing a simple XML file parser using LINQ to XML.
I want to have a TreeNode object (i.e a simple Tree structure) for each element in the XML. I want each element to be strongly typed.
It looks ugly and redundant compared to the simple looping approach I was using before (using System.XML). Is there a way to strip out the redundancies here?
XElement ops = XElement.Load(#"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>();
domain.Root = new TreeNode<Element>();
var cells =
from cell in ops.Elements("cell")
select new
{
TreeNodeObj = new TreeNode<Element>
(new Cell((string)cell.Attribute("name"), (string)cell.Attribute("name"), null)),
XElem = cell
};
foreach (var cell in cells)
{
domain.Root.AddChild(cell.TreeNodeObj);
var agents =
from agent in cell.XElem.Elements("agent")
select new
{
TreeNodeObj = new TreeNode<Element>
(new Agent((string)agent.Attribute("name"), (string)agent.Attribute("name"), null)),
XElem = agent
};
foreach (var agent in agents)
{
cell.TreeNodeObj.AddChild(agent.TreeNodeObj);
var nas =
from na in agent.XElem.Elements("node-agent")
select new
{
TreeNodeObj = new TreeNode<Element>
(new NodeAgent((string)na.Attribute("name"), (string)na.Attribute("name"), null)),
XElem = agent
};
foreach (var na in nas)
{
agent.TreeNodeObj.AddChild(na.TreeNodeObj);
}
}
}
It is hard to answer this fully without sample data and actual types, but I would refactor it like below.
From the original example, I'm assuming we don't want to mess with the constructors of the entities (Agent etc), and that we want to retain the separate "TreeNode<T>" model, putting our entities inside the tree (rather than changing the entities to model things as associated collections). I've also assumed that we can take more liberties with TreeNode<T> than we can with the entities, so I've introduced a constructor that accepts IEnumerable<...>, since this allows use with LINQ sub-queries:
XElement ops = XElement.Load(#"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>(
from cell in ops.Elements("cell")
select new TreeNode<Element>(
new Cell(
(string)cell.Attribute("name"),
(string)cell.Attribute("name"), null
),
from agent in cell.Elements("agent")
select new TreeNode<Element>(
new Agent(
(string)agent.Attribute("name"),
(string)agent.Attribute("name"), null
),
from na in agent.Elements("node-agent")
select new TreeNode<Element>(
new NodeAgent(
(string)na.Attribute("name"),
(string)na.Attribute("name"), null
)
)
)
)
);
With framework code below:
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Tree<T>
{
public TreeNode<T> Root { get; set; }
public Tree() { }
public Tree(IEnumerable<TreeNode<T>> children)
{
Root = new TreeNode<T>(children);
}
}
class TreeNode<T>
{
private List<TreeNode<T>> children;
public IList<TreeNode<T>> Children
{
get
{
if (children == null) children = new List<TreeNode<T>>();
return children;
}
}
private readonly T value;
public TreeNode() { }
public TreeNode(T value) { this.value = value; }
public TreeNode(T value, IEnumerable<TreeNode<T>> children)
: this(children)
{
this.value = value;
}
public TreeNode(IEnumerable<TreeNode<T>> children)
{
children = new List<TreeNode<T>>(children);
}
}
class Element { }
class Cell : Element {
public Cell(string x, string y, string z) { }
}
class Agent : Element {
public Agent(string x, string y, string z) { }
}
class NodeAgent : Element {
public NodeAgent(string x, string y, string z) { }
}
static class Program
{
static void Main()
{
XElement ops = XElement.Load(#"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>(
from cell in ops.Elements("cell")
select new TreeNode<Element>(
new Cell(
(string)cell.Attribute("name"),
(string)cell.Attribute("name"), null
),
from agent in cell.Elements("agent")
select new TreeNode<Element>(
new Agent(
(string)agent.Attribute("name"),
(string)agent.Attribute("name"), null
),
from na in agent.Elements("node-agent")
select new TreeNode<Element>(
new NodeAgent(
(string)na.Attribute("name"),
(string)na.Attribute("name"), null
)
)
)
)
);
}
}
Without your classes and source xml, it's quite hard to provide you with the exact code you're after, but here's how I like to structure my XML parsing:
XDocument d = XDocument.Parse(#"<a id=""7""><b><c name=""foo""/><c name=""bar""/></b><b/><b2/></a>");
var ae = d.Root;
var a = new A
{
Id = (int)ae.Attribute("id"),
Children = new List<B>(ae.Elements("b").Select(be => new B
{
Children = new List<C>(be.Elements("c").Select(ce => new C
{
Name = (string)ce.Attribute("name")
}))
}))
};
Given the xml:
<a>
<b>
<c name="foo"/>
<c name="bar"/>
</b>
<b/>
<b2/>
</a>
and the classes:
class A
{
public int Id { get; set; }
public List<B> Children { get; set; }
}
class B
{
public List<C> Children { get; set; }
}
class C
{
public string Name { get; set; }
}