C# Looping through nested LINQ select statements - c#

The sample below is parsing an XML document then looping through the members and storing them in a list of objects (The data ultimately ends up in an SQL database):
public static void Parse(XDocument xml)
{
XNamespace ns = "http://somenamespace.com/ns";
var Locations =
from Continents in xml.Descendants(ns + "Continent")
from Countries in Continents.Elements(ns + "Country")
select new
{
Continent1 = (string) Continents.Element(ns + "Europe"),
Country1 = (string) Countries.Element(ns + "United_Kingdom"),
Cities = from Cities in Countries.Elements(ns + "City")
select new
{
City1 = (string) Cities.Element(ns + "London")
}
};
List<Location> locationColl = new List<Location>();
loc_Entity_FrameworkContainer context = new loc_Entity_FrameworkContainer();
var i = 0;
foreach (var location in Locations)
{
Location l = new Location();
locationColl.Add(l);
locationColl[i].Continent = (string) location.Continent1;
locationColl[i].Country = (string) location.Country1;
locationColl[i].City = (string) location.City1; // Can't access "City1"
context.Location.Add(locationColl[i]);
i++;
}
context.SaveChanges();
}
The statement: locationColl[i].City = (string)location.City1;
doesn't find "City1". (This is the issue, I can't access all the members from "Locations" in one loop)
Location Class:
namespace locationProject
{
using System;
using System.Collections.Generic;
public partial class Location
{
public string Continent { get; set; }
public string Country { get; set; }
public string City { get; set; }
}
}
XML Example:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:ns="http://somenamespace.com/ns">
<ns:Continent>
<ns:Europe>21c99a56-4b3d-4571-802a-76cdb6b81a01</ns:Europe>
<ns:Country>
<ns:United_Kingdom>eb2e9eec-dc3b-4636-bcf5-dba0024e62f3</ns:United_Kingdom>
<ns:City>
<ns:London>109b48ec-d829-4a87-b200-4dc9a94db48c</ns:London>
</ns:City>
</ns:Country>
</ns:Continent>
<ns:Continent>
<ns:Europe>a11ed925-dc0d-4dfd-b1c2-52eb697ad689</ns:Europe>
<ns:Country>
<ns:United_Kingdom>a61d02ef-7b80-4390-926a-49c6d9af9634</ns:United_Kingdom>
<ns:City>
<ns:London>dbb9c5cc-b08f-4223-b32c-acb4ed9ce97c</ns:London>
</ns:City>
</ns:Country>
</ns:Continent>
</feed>
I'm trying to find a way of looping through all the elements (Continent1, Country1, City1) that doesn't involve multiple loops and doesn't break the nested structure of the LINQ statements.
There are questions on here similar to this one, but I haven't found one I understand well enough to integrate with my code.
Thanks a lot!

Your anonymous type contained in the Locations list has a .Cities property that contains a City1 member:
Cities = from Cities in Countries.Elements(ns + "City")
select new
{
City1 = (string) Cities.Element(ns + "London")
}
Try this:
var Locations =
from Continents in xml.Descendants(ns + "Continent")
from Countries in Continents.Elements(ns + "Country")
from Cities in Countries.Elements(ns + "City")
select new
{
Continent1 = (string) Continents.Element(ns + "Europe"),
Country1 = (string) Countries.Element(ns + "United Kingdom"),
City1 = (string) Cities.Element(ns + "London")
};

Related

How do i add database values to a dictionary containing a class as object

I have a dictionary containg a string as TKey and a class "Component" as TValue.
My problem is that a want to add database values and xml values into the class fields.
This is my class Component:
public class Component
{
public string ComponentNr { get; set; }
public string Omschrijving { get; set; }
public int Aantal { get; set; }
public int Pos { get; set; }
}
I already have filled the fields ComponentNr and Pos with xml attribute values and now i want to get the "Aantal" and "Omschrijving" from the database where Database value "artcode" is ComponentNr
the query:
SqlCommand dgCommand = new SqlCommand("select g.artcode, i.Description, sum(g.aantal*-1) as aantal " +
"from gbkmut g " +
"join Items i on g.artcode = i.ItemCode " +
"where 1=1 and g.project=#projectnr and g.reknr=3000 and g.bud_vers='MRP' and g.operation='VOORSMD' and g.artcode !='H MAN' and i.Description not like 'pcb %' " +
"group by g.artcode, i.Description, g.aantal ", conn);
This is what i have at the moment to fill the Dictionary with xml attribute value:
Dictionary<string, Component> resultaten = componenten;
List<string> files = fileList;
string file;
file = files.Find(x => x.Contains(faberNr));
XDocument xdoc = XDocument.Load(file);
List<Component> components = xdoc.Descendants("Part").Select(x => new Component()
{
ComponentNr = (string)x.Elements().Where(y => y.Attribute("PartsName") != null)
.Select(y => (string)y.Attribute("PartsName")).FirstOrDefault(),
Pos = (int)x.Descendants().Where(y => y.Attribute("Setno") != null)
.Select(y => (int)y.Attribute("Setno")).FirstOrDefault()
}).ToList();
resultaten = components.GroupBy(x => x.ComponentNr, y => y).ToDictionary(x => x.Key, y => y.FirstOrDefault());
return resultaten;
Example:
My expected output is:
Key = 38292000
Value = Component
Aantal = 16
ComponentNr = 38292000
Omschrijving = Omschrijving123
Pos = 12
My actual output is:
Key = 38292000
Value = Component
Aantal = 0
ComponentNr = 38292000
Omschrijving = null
Pos = 12
So, step 1 is to populate a Dictionary<string, Component> from xml. Then step 2 is to run a database query to finish filling out the Component objects that are stored in the Dictionary?
ComponentNr is the key into the Dictionary, and you have ComponentNr from your database query in the "artcode" field, so you would just look up the value from the Dictionary using "artcode" then modify it.
// reader is an instance of SqlDataReader you got from the SqlCommand.
// resultaten is the Dictionary<string, Component> that you populated earlier.
// If these columns can be null in the database, don't forget to check them for DBNull.Value.
string componentNr = Convert.ToString(reader["artcode"]);
if (resultaten.TryGetValue(componentNr, out Component value))
{
value.Aantal = Convert.ToInt32(reader["aantal"]);
value.Omschrijving = Convert.ToString(reader["Description"]);
}
else
{
// Component exists in the database but not in the Dictionary.
}

C# XML multiple subchild

Good day! Im trying to parse XML subchild using dataset. The thing is its not reading the "SiteCode" when it has multiple value.
for example:
string filePath = #"" + _clsPathIntervalSttngs.localPath + "/" + "hehe.xml";
DataSet dataSet = new DataSet()
dataSet.ReadXml(filePath, XmlReadMode.InferSchema);
// Then display informations to test
foreach (DataTable table in dataSet.Tables)
{
Console.WriteLine(table);
for (int i = 0; i < table.Columns.Count; ++i)
{
Console.Write("\t" + table.Columns[i].ColumnName.Substring(0, Math.Min(6, table.Columns[i].ColumnName.Length)));
Console.WriteLine();
}
foreach (var row in table.AsEnumerable())
{
for (int i = 0; i < table.Columns.Count; ++i)
{
Console.Write("\t" + row[i]);
}
Console.WriteLine();
}
}
this is what it is returning.
Its returning a 0 value and selecting the Product instead of sitecode.
Where did i go wrong?
You might have to check the code because I just took something similar I had lying around and changed it to look at your document hierarchy. I also didn't use a DataSet. Consider the following code:
var filePath = "<path to your file.xml>";
var xml = XDocument.Load(filePath);
var items = from item in xml.Descendants("Product").Elements()
select item.Value;
Array.ForEach(items.ToArray(), Console.WriteLine);
That should show you the values of each element under product. If you want the whole element, remove the .Value in the select clause of the LINQ query.
Update
I'm now projecting to an anonymous type. You'll get one of these for each Product element in the file.
var items = from item in dataset.Descendants("Product")
select new
{
RefCode = item.Element("RefCode").Value,
Codes = string.Join(", ", item.Elements("SiteCode").Select(x => x.Value)),
Status = item.Element("Status").Value
};
Array.ForEach(items.ToArray(), Console.WriteLine);
I have flattened the codes to a comma separated string but you can keep the IEnumerable or ToList it as you wish.
Using xml Linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication51
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XElement doc = XElement.Load(FILENAME);
List<Product> products = doc.Descendants("Product").Select(x => new Product()
{
refCode = (string)x.Element("RefCode"),
siteCode = x.Elements("SiteCode").Select(y => (int)y).ToArray(),
status = (string)x.Element("Status")
}).ToList();
}
}
public class Product
{
public string refCode { get; set; }
public int[] siteCode { get; set; }
public string status { get; set; }
}
}

Flatten XML, change node then add to dictionary in C#

Here is part of my very long XML (over 223 nodes)
<ApplicationExtraction>
<ApplicationDate>10/06/2015</ApplicationDate>
<Status>Application Received</Status>
<EquipmentType>Equipment</EquipmentType>
<GetActiveLeaseApplicationParties>
<Item>
<RelationshipType>Primary Lessee</RelationshipType>
<PartyNumber>20000107</PartyNumber>
<FirstName>Parvesh</FirstName>
<LastName>Musharuf</LastName>
<DateOfBirth>12/12/1993</DateOfBirth>
<CreationDate>10/06/2015</CreationDate>
</Item>
<Item>
<RelationshipType>Co-Lessee</RelationshipType>
<PartyNumber>20000108</PartyNumber>
<IsCorporate>No</IsCorporate>
<FirstName>Pary</FirstName>
<LastName>Mushroom</LastName>
<DateOfBirth>1/12/1953</DateOfBirth>
<CreationDate>10/06/2015</CreationDate>
</Item>
</GetActiveLeaseApplicationParties>
</ApplicationExtraction>
I created dictionary Dictionary<string, string> xmlData = new Dictionary<string, string>(); and want to add node as key and node value as value.
I got part of it work until 2nd Item child nodes. It gives me the error of "An item with the same key has already been added". Now I want to add sequence number to the node Item so that I won't get this error. Ideally, I want something like this:
ApplicationExtraction.GetActiveLeaseApplicationParties.Item1.RelationshipType
ApplicationExtraction.GetActiveLeaseApplicationParties.Item1.PartyNumber
ApplicationExtraction.GetActiveLeaseApplicationParties.Item1.FirstName
ApplicationExtraction.GetActiveLeaseApplicationParties.Item2.RelationshipType
ApplicationExtraction.GetActiveLeaseApplicationParties.Item2.PartyNumber
Is it possible to achieve this?
I tried to catch the error and split the string to put number in but don't know how to increase the sequence probably instead I got:
ApplicationExtraction.GetActiveLeaseApplicationParties.Item1.RelationshipType
ApplicationExtraction.GetActiveLeaseApplicationParties.Item2.PartyNumber
ApplicationExtraction.GetActiveLeaseApplicationParties.Item3.FirstName
This is my code. result only contain path(node) and value
foreach (var p in result)
{ try
{ key = p.Path;
value =p.Value;
xmlData.Add(key,value); }
catch (Exception exc)
{ i++;
if (exc.Message == "An item with the same key has already been added.")
{
pos = key.IndexOf("Item");
if (pos !=-1 )
{
strTemp1 = key.Substring(0,key.IndexOf("Item")+4);
strTemp2 = key.Substring(pos + 4,key.Length - pos - 4);
}
key = strTemp1 + "[" + i.ToString() + "]" + strTemp2;
value =p.Value;
xmlData.Add(key,value);
}
}
Did you try to step through in debugger?
Assuming i equals 0 at the top of the loop:
For the first iteration you will add Item.RelationshipType, Item.PartyNumber, etc.
For the second item, you get exception at Item.RelationshipType and you will instead use Item[1].RelationshipType.
For that second item itself, you will then get exception for Item.PartyNumber as well, i will be incremented to 2 and the key you will use will be Item[2].PartyNumber.
This is why you are seeing the keys that you see.
There are multiple ways to get the correct key. One is to keep track of current item number, which you can increment when you see the <item> tag, and use it for all sub-elements.
you can create a List of Tuple
which can collect all the values of the xml.
add each item in the xml,to the list
List.Add(Tuple.Create((Relationship.value,PartyNumber.value...);
To fetch each one in the list, give lst[i].item1,lst[i].item2 which will provide you the xml Item[i].RelationshipValue,Item[i].PartyNumbervalue
Below is how I would do it. If you are getting an error that the key already exists it is due to your dictionary have duplicate keys. I would use the PartyNumber as the key in the dictionary. if you have more than one entry for a part number than the dictionary must be defined as Dictionary>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication61
{
class Program
{
static void Main(string[] args)
{
string xml =
"<ApplicationExtraction>" +
"<ApplicationDate>10/06/2015</ApplicationDate>" +
"<Status>Application Received</Status>" +
"<EquipmentType>Equipment</EquipmentType>" +
"<GetActiveLeaseApplicationParties>" +
"<Item>" +
"<RelationshipType>Primary Lessee</RelationshipType>" +
"<PartyNumber>20000107</PartyNumber>" +
"<FirstName>Parvesh</FirstName>" +
"<LastName>Musharuf</LastName>" +
"<DateOfBirth>12/12/1993</DateOfBirth>" +
"<CreationDate>10/06/2015</CreationDate>" +
"</Item>" +
"<Item>" +
"<RelationshipType>Co-Lessee</RelationshipType>" +
"<PartyNumber>20000108</PartyNumber>" +
"<IsCorporate>No</IsCorporate>" +
"<FirstName>Pary</FirstName>" +
"<LastName>Mushroom</LastName>" +
"<DateOfBirth>1/12/1953</DateOfBirth>" +
"<CreationDate>10/06/2015</CreationDate>" +
"</Item>" +
"</GetActiveLeaseApplicationParties>" +
"</ApplicationExtraction>";
XDocument doc = XDocument.Parse(xml);
Dictionary<int, Item> dict = new Dictionary<int, Item>();
foreach (XElement item in doc.Descendants("Item").AsEnumerable())
{
Item newItem = new Item() {
relationshipType = item.Element("RelationshipType").Value,
partyNumber = int.Parse(item.Element("PartyNumber").Value),
isCorporate = item.Element("IsCorporate") == null ? false :
item.Element("IsCorporate").Value == "Yes" ? true : false,
firstName = item.Element("FirstName").Value,
lastName = item.Element("LastName").Value,
dateOfBirth = DateTime.Parse(item.Element("DateOfBirth").Value),
creationDate = DateTime.Parse(item.Element("CreationDate").Value)
};
dict.Add(newItem.partNumber, newItem);
}
}
public class Item
{
public string relationshipType { get; set; }
public int partyNumber { get; set; }
public Boolean isCorporate { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public DateTime dateOfBirth { get; set; }
public DateTime creationDate { get; set; }
}
}
}

Concat or Join list of "names" in Linq

Using WCF RIA I have a query that returns a Query of names
public class WitnessInfo
{
[Key]
public Guid WCFId { get; set; }
public string witnessName { get; set; }
public string AllNames {get; set;}
}
Here's my Linq Query
[Query]
public IQueryable<WitnessInfo> getWitnessInfo(int? id)
{
IQueryable<WitnessInfo> witnessQuery = from witness in this.Context.witness
where witness.DAFile.Id == id
select new WitnessInfo
{
WCFId = Guid.NewGuid(),
witnessName = witness.Person.FirstName,
};
return witnessQuery;
}
I want to take all the names and return them in a single string i.e "John, James, Tim, Jones". Tried taking AllNames and looping through but that didn't work. Any suggestions?
First grab all of the information that you need in a single query, then use String.Join to map the collection of names to a single string:
var firstQuery = from witness in Context.witness
where witness.DAFile.Id == id
select new
{
WCFId = Guid.NewGuid(),
witnessName = witness.Person.FirstName,
Names = Context.witness.Select(w => w.FirstName),
})
.AsEnumerable(); //do the rest in linq to objects
var finalQuery = from witness in firstQuery
//do the string manipulation just once
let allNames = string.Join(", ", witness.Names)
select new WitnessInfo
{
WCFId = witness.WCFId,
witnessName = witness.witnessName,
AllNames = allNames,
});
By having the AllNames property in the WitnessInfo class, it is seems like you want each WitnessInfo object to contain the all of the squence names again and again repeatedly, and if this is your case then do it like that:
var names = (from witness in this.Context.witness
select witness.Person.FirstName).ToArray();
var allNames = string.Join(", ", names);
IQueryable<WitnessInfo> witnessQuery = from witness in this.Context.witness
where witness.DAFile.Id == id
select new WitnessInfo
{
WCFId = Guid.NewGuid(),
witnessName = witness.Person.FirstName,
AllNames = allNames
};
You can concatenate like this:
string.Join(", ", getWithnessInfo(666).Select(wq => wq.witnessName))
this.Context.witness.Select(a => a.Person.Firstname).Aggregate((a, b) => a + ", " + b);

Parse XML with LINQ to get child elements

<?xml version="1.0" standalone="yes"?>
<CompanyInfo>
<Employee name="Jon" deptId="123">
<Region name="West">
<Area code="96" />
</Region>
<Region name="East">
<Area code="88" />
</Region>
</Employee>
</CompanyInfo>
public class Employee
{
public string EmployeeName { get; set; }
public string DeptId { get; set; }
public List<string> RegionList {get; set;}
}
public class Region
{
public string RegionName { get; set; }
public string AreaCode { get; set; }
}
I am trying to read this XML data, so far I have tried this:
XDocument xml = XDocument.Load(#"C:\data.xml");
var xElement = xml.Element("CompanyInfo");
if (xElement != null)
foreach (var child in xElement.Elements())
{
Console.WriteLine(child.Name);
foreach (var item in child.Attributes())
{
Console.WriteLine(item.Name + ": " + item.Value);
}
foreach (var childElement in child.Elements())
{
Console.WriteLine("--->" + childElement.Name);
foreach (var ds in childElement.Attributes())
{
Console.WriteLine(ds.Name + ": " + ds.Value);
}
foreach (var element in childElement.Elements())
{
Console.WriteLine("------->" + element.Name);
foreach (var ds in element.Attributes())
{
Console.WriteLine(ds.Name + ": " + ds.Value);
}
}
}
}
This enables me to get each node, its attribute name and value and so I can save these data into the relevant field in database, but this seems a long winded way and
not flexible, for instance if the XML structure changes all those foreach statements needs revisiting, also it is difficult to filter the data this way,
I need to write certain if statements to filter the data (e.g get employees from West only etc...)
I was looking for a more flexible way, using linq, something like this:
List<Employees> employees =
(from employee in xml.Descendants("CompanyInfo")
select new employee
{
EmployeeName = employee.Element("employee").Value,
EmployeeDeptId = ?? get data,
RegionName = ?? get data,
AreaCode = ?? get data,,
}).ToList<Employee>();
But I am not sure how I can get the values from the child nodes and apply the filtering (to get the certain employees only). Is this possible? Any help is appreciated.
Thanks
var employees = (from e in xml.Root.Elements("Employee")
let r = e.Element("Region")
where (string)r.Attribute("name") == "West"
select new Employee
{
EmployeeName = (string)e.Attribute("employee"),
EmployeeDeptId = (string)e.Attribute("deptId"),
RegionName = (string)r.Attribute("name"),
AreaCode = (string)r.Element("Area").Attribute("code"),
}).ToList();
But it will still require query revision when XML file structure changes.
Edit
Query for multiple regions per employee:
var employees = (from e in xml.Root.Elements("Employee")
select new Employee
{
EmployeeName = (string)e.Attribute("employee"),
DeptId = (string)e.Attribute("deptId"),
RegionList = e.Elements("Region")
.Select(r => new Region {
RegionName = (string)r.Attribute("name"),
AreaCode = (string)r.Element("Area").Attribute("code")
}).ToList()
}).ToList();
You can then filter the list for employees from given region only:
var westEmployees = employees.Where(x => x.RegionList.Any(r => r.RegionName == "West")).ToList();
You can track the structure:
from employee in xml
.Element("CompanyInfo") // must be root
.Elements("Employee") // only directly children of CompanyInfo
or less strictly
from employee in xml.Descendants("Employee") // all employees at any level
And then get the information you want:
select new Employee
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
RegionName = employee.Element("Region").Attribute("name").Value,
AreaCode = employee.Element("Region").Element("Area").Attribute("code").Value,
}
And with the additional info about multiple regions, assuming a List<Region> Regions property:
select new Employee
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
//RegionName = employee.Element("Region").Attribute("name").Value,
//AreaCode = employee.Element("Region").Element("Area").Attribute("code").Value,
Regions = (from r in employee.Elements("Region") select new Region
{
Name = r.Attribute("name").Value,
Code = r.Element("Area").Attribute("code").Value,
}).ToList();
}
You can do the selection in one query and then the filtering in second or combine them both to one query:
Two queries:
// do te transformation
var employees =
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
Regions = from region in employee.Elements("Region")
select new
{
Name = region.Attribute("name").Value,
AreaCode = region.Element("Area").Attribute("code").Value,
}
};
// now do the filtering
var filteredEmployees = from employee in employees
from region in employee.Regions
where region.AreaCode == "96"
select employee;
Combined one query (same output):
var employees2 =
from selectedEmployee2 in
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
Regions = from region in employee.Elements("Region")
select new
{
Name = region.Attribute("name").Value,
AreaCode = region.Element("Area").Attribute("code").Value,
}
}
from region in selectedEmployee2.Regions
where region.AreaCode == "96"
select selectedEmployee2;
But there is one little thing you should consider adding. For robustness, you need to check existence of your elements and attributes then the selection will look like that:
var employees =
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = (employee.Attribute("name") != null) ? employee.Attribute("name").Value : string.Empty,
EmployeeDeptId = (employee.Attribute("deptId") != null) ? employee.Attribute("deptId").Value : string.Empty,
Regions = (employee.Elements("Region") != null)?
from region in employee.Elements("Region")
select new
{
Name = (region.Attribute("name")!= null) ? region.Attribute("name").Value : string.Empty,
AreaCode = (region.Element("Area") != null && region.Element("Area").Attribute("code") != null) ? region.Element("Area").Attribute("code").Value : string.Empty,
}
: null
};

Categories