XDocument - get list of node values - silverlight

I have a XDocument something like this:
<LookupList>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_test</FieldName>
<LookupText>open</LookupText>
</ImageInfo1>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_test</FieldName>
<LookupText>Waiting for input</LookupText>
</ImageInfo1>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_NEW_NAME</FieldName>
<LookupText>Closed</LookupText>
</ImageInfo1>
</LookupList>
I want to get
number and values of unique FieldName(s) and
a list of LookupText for each FieldName.
Can someone give me a hint how to go about it?

First you need to know how to read the elements from your XDocument:
public void ReadXDocument(XDocument doc)
{
foreach (XElement el in doc.Descendants("ImageInfo1"))
{
string fieldid = el.Element("FieldID").Value;
string fieldName = el.Element("FieldName").Value;
string lookupText = el.Element("LookupText").Value;
}
Once you know that, using Linq to achieve your goal is relatively straight forward.
This should give you a list of distinct FieldNames:
List<String> distinctNames = doc.Descendants("ImageInfo1")
.Select(o => o.Element("FieldName").Value)
.Distinct().ToList();
And this should give you a collection of LookupText values for each FieldName
IEnumerable groups = doc.Descendants("ImageInfo1")
.GroupBy(o => o.Element("FieldName").Value)
.Select(o => new { Key = o.Key, Lookups = o.ToList() });
}

Related

Dapper One to Many Mapping Logic

The dapper tutorial gives this example to help a user with Multi Mapping (One to Many)
While this works I am curious why they have you store the orders in the dictionary but then in the end they use a linq.Distinct() and return from the list. It seems like it would be cleaner to just return the ordersDictionary.Values as the dictionary logic ensures no duplicates.
//Tutorial
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
List<Order> list = connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID")
.Distinct()
.ToList();
return list;
}
//my suggestion
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
//change 1 no need to store into list here
connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID"); //change 2 remove .Distinct().ToList()
return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
}
I'm the author of this tutorial: https://dapper-tutorial.net/query#example-query-multi-mapping-one-to-many
why they have you store the orders in the dictionary
A row is returned for every OrderDetail. So you want to make sure to add the OrderDetail to the existing Order and not create a new one for every OrderDetail. The dictionary is used for performance to check if the Order has been already created or not.
it would be cleaner to just return the ordersDictionary.Values
How will your query return dictionary values?
Of course, if you are in a method such as yours, you can do
var list = orderDictionary.Values;
return list;
But how to make this Connection.Query return dictionary values? An order is returned for every row/OrderDetail, so the order will be returned multiple times.
Outside the Query, your dictionary solution works great and is even a better solution for performance, but if you want to make your Query return the distinct list of orders without using Distinct or some similar method, it's impossible.
EDIT: Answer comment
my suggestion return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
Thank you for your great feedback, it's always appreciated ;)
It would be weird in a tutorial to use what the query returns when there is no relationship but use the dictionary for one to many relationships
// no relationship
var orders = conn.Query<Order>("", ...).Distinct();
// one to many relationship
conn.Query<Order, OrderDetail>("", ...);
var orders = orderDictionary.Values.ToList();
Your solution is better for performance the way you use it, there is no doubt about this. But this is how people usually use the Query method:
var orders = conn.Query("", ...).Distinct();
var activeOrders = orders.Where(x => x.IsActive).ToList();
var inactiveOrders = orders.Where(x => !x.IsActive).ToList();
They use what the Query method returns.
But again, there is nothing wrong with the way you do it, this is even better if you can do it.

How to check a specific Value in multiple Documents in Firestore with Flutter

I'm trying to check if any document from my collection contains a Value with a specificTime. But Im doing something wrong.
Im searching for a way to check if any Document in my Collection contains the "DateTime.now().year" in the "CreatedAt" Field.
List documents = [];
#override
void initState() {
Firestore.instance.collection('Collection').getDocuments().then((snapshot) {
List<DocumentSnapshot> allDocs = snapshot.documents;
List<DocumentSnapshot> yearDocs = allDocs.where((e) => e.data['createdAt'].toDate.year() == DateTime.now().year).toList();
// iterate over your yearDocs, and add the data to documents
yearDocs.forEach((item){
documents.add(item);
});
});
super.initState();
}
I want to set a List with Documents where the "Field" contains the "DateTime.now().year". Then i check if the List is emtpty.
documents.length >= 1? Container(
child: Text('works'),
) : SizedBox(),
Answer 3.0
Your answer lies in the following
Fetch the createdAt element from the DataSnapshot array which is allDocs
There is a catch that createdAt brings in the timestamp in the Firebase TimeStamp instance format, that is => TimeStamp(seconds=int, nanoseconds=int), for example: Timestamp(seconds=1593504718, nanoseconds=706167000)
What we care about is getting the seconds, which is nothing but a timestamp only
Use the seconds and get the Year from it, and store it in your yearDocs
To get the data in the form of year, we do this
allDocs.forEach((item) => print(item.data["createdAt"].seconds));
// gives your timestamp
// 1593504718
// 1593504699
// 1593259892
// 1596121191
We can get the year via doing this
new DateTime.fromMillisecondsSinceEpoch(1593504718 * 1000).year
Combining all of these, let us code:
List documents = [];
#override
void initState() {
Firestore.instance.collection('Collection').getDocuments().then((snapshot) {
List<DocumentSnapshot> allDocs = snapshot.documents;
// now here we get the data in respect of year
List<DocumentSnapshot> yearDocs = allDocs.where((e) =>
DateTime.fromMillisecondsSinceEpoch(e.data["createdAt"].seconds * 1000).year == DateTime.now().year).toList();
print(yearDocs.length);
// iterate over your yearDocs, and add the data to documents
yearDocs.forEach((item){
documents.add(item);
});
print(documents.length);
});
}
Rest will be fine, you can populate the data as per the documents.length. Please let me know if that helped you. :) This will also help you gain some insight => DateTime class
I think you’re missing data
yearDocs = allDocs.where((e) => e.data['createdAt'].toDate.year() == DateTime.now()).toList();

Convert List<String> to List<dynamic>

I have created a list in my program as a String list as below.
List<String> finalcats = [];
Then I added used shred preferences since I want to use it in another page
prefs.setStringList("postcategories", finalcats);
Now I want to retrieve this in another page.
prefs.getStringList("postcategories") works fine for retrieving but I want it as a List .
How can I convert the List finalcats = []; to List = [];
The getStringList method always return List<String>. You can cast this using as keyword. For example
List<String> categories = prefs.getStringList("postcategories");
List<dynamic> dynamicCategories = categories as List<dynamic>;
Or you can loop over the list and convert each element into a Map like this
categories.map((value) => {'key': value}).toList();

WPF list<t> sort

my first post here.
i have a list"<"frameworkelement> that i'm populating with a select process. each frameworkelement has a uid that holds its ZOrder.
i need to sort these by the ZOrder from lowest to highest. i can get this using a listbox and adding the Uid's like this:
//Add Object Uid's
ListBox lstTempOrder = new ListBox();
foreach(FrameworkElement feObject in MainWindow.Data.SelectedObjects)
{
lstTempOrder.Items.Add(feObject.Uid);
}
//Reorder from 0 to above of the ZIndexes
lstTempOrder.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("", System.ComponentModel.ListSortDirection.Ascending));
but i need to do this with a List"<"FrameWorkElement> and Sort.
Here is the code where i populate the List"<"T> (SelectedObjects and CopyObjectsCollections are List"<"FrameWorkElement>" lists.
foreach(FrameworkElement feObject in MainWindow.Data.SelectedObjects)
{
MainWindow.Data.CopyObjectsCollection.Add(feObject);
}
i've looked at CollectionViewSource and IComparer but i can't really make any sense of it.
I might have miss-read your question, but if you just want to sort your List<T>, then why don't you just use the LinQ OrderBy method?
MainWindow.Data.CopyObjectsCollection =
MainWindow.Data.CopyObjectsCollection.OrderBy(f => f.Uid).ToList();
If that sorts it the wrong way round for your requirements, then you can use this:
MainWindow.Data.CopyObjectsCollection =
MainWindow.Data.CopyObjectsCollection.OrderByDescending(f => f.Uid).ToList();
UPDATE >>>
OrderBy is a LinQ extension method. Add using System.Linq; at the top of your class to use it. f relates to an instance of your FrameworkElement object. The above lambda expression basically means 'sort using the Uid property values'.
UPDATE 2 >>>
The OrderBy method does not alter the original collection... that is why my example sets the collection to the result of the OrderBy method. See this basic example:
List<FrameworkElement> elements = new List<FrameworkElement>();
elements.Add(new FrameworkElement() { Uid = "Object1003-1" });
elements.Add(new FrameworkElement() { Uid = "Object1002-2" });
elements.Add(new FrameworkElement() { Uid = "Object1002-1" });
elements.Add(new FrameworkElement() { Uid = "Object1001-1" });
elements.Add(new FrameworkElement() { Uid = "Object1001-3" });
elements.Add(new FrameworkElement() { Uid = "Object1001-2" });
string result = string.Join(", ", elements.Select(f => f.Uid));
elements = elements.OrderBy(f => f.Uid).ToList();
string orderedResult = string.Join(", ", elements.Select(f => f.Uid));
By comparing the values of result and orderedResult you can see that this orders them perfectly.
UPDATE 3 (and hopefully the LAST one) >>>
Dude, you need to learn about Lambda expressions... take a look at the Lambda Expressions (C# Programming Guide) page at MSDN for more information.
elements = elements.OrderBy(f => f.Uid).ToList();
The f in this Lambda expression is declared in this expression before the '=>'. It is fairly standard to name these parameters with one letter like Exceptions, but we could name it anything:
elements = elements.OrderBy(frameworkElement => frameworkElement.Uid).ToList();

Parse XML with Linq

I have the following XML document which I would like to parse into a DataSet.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
<Item>
<Field Name="ID">767147519</Field>
<Field Name="Name">Music</Field>
<Field Name="Path">Family\Music</Field>
<Field Name="Type">Playlist</Field>
</Item>
</Response>
I am wanting to get the attribute values for ID, Name, and Path.
The following is what I have attempted:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Item") Select c
For Each result In name
Dim str1 = result.Attribute("ID").Value 'Returns Nothing and causes a validation error
Dim str2 = result.Value ' Returns all the attribute values in one long string (ie "767147519MusicFamilyPlaylist")
Next
Any help would be greatly appreciated.
Thanks,
Matt
EDIT:
Following one of the answers below, I have been attempting to implement an anonymous type in my Linq, however I keep encountering the error
Object reference not set to an
instance of an object.
My updated code is as follows:
Dim name = From c In loaded.Descendants("Item") Select c Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
Dim Id As String = String.Empty
For Each result In name
Id = result.sID
Next
I think this error means that the attribute ("ID") cannot be located, so I have attempted several variations of this with similar results.
Is anyone able to identify where I am going wrong and point me in the right direction.
Thanks,
Matt
You can use XPath:
Dim data = From item In loaded.Descendants("Item")
Select
ID = item.XPathSelectElement("Field[#Name='ID']").Value,
Name = item.XPathSelectElement("Field[#Name='Name']").Value,
Path = item.XPathSelectElement("Field[#Name='Path']").Value,
Type = item.XPathSelectElement("Field[#Name='Type']").Value
(Be sure to import the System.Xml.XPath namespace)
Or to add it directly to a DataTable:
Dim dt As New DataTable()
dt.Columns.Add("ID")
dt.Columns.Add("Name")
dt.Columns.Add("Path")
dt.Columns.Add("Type")
For Each item In loaded.Descendants("Item")
dt.Rows.Add(
item.XPathSelectElement("Field[#Name='ID']").Value,
item.XPathSelectElement("Field[#Name='Name']").Value,
item.XPathSelectElement("Field[#Name='Path']").Value,
item.XPathSelectElement("Field[#Name='Type']").Value
)
Next
Another one solution with anonymous types:
var doc = XDocument.Load("c:\\test");
var list = doc.Root
.Elements("Item")
.Select(item =>
new
{
Id = item.Elements("Field").Where(e => e.Attribute("Name").Value == "ID").Select(e => e.Value).FirstOrDefault(),
Path = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Path").Select(e => e.Value).FirstOrDefault(),
Name = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Name").Select(e => e.Value).FirstOrDefault(),
})
.ToArray();
foreach (var item in list)
{
var id = item.Id;
var name = item.Name;
}
Ugly expression inside new operator can be shorted with next anonymous function:
Func<XElement, string, string> getAttrValue = (node, attrName) =>
{
return node.Elements("Field")
.Where(e => e.Attribute("Name").Value == attrName)
.Select(e => e.Value)
.FirstOrDefault();
};
Then new operator looks like:
new
{
Id = getAttrValue(item, "ID"),
Path = getAttrValue(item, "Path"),
Name = getAttrValue(item, "Name"),
}
Here is my attempt at solution to your problem. I just noticed that you wish to go with as much LINQ as possible so I've structured my LINQ query accordingly. Please note result type (for "IDs") will be IEnumerable() i.e. you will need to run a for each loop on it to get individual ids even with a single item:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim IDs = From items In loaded.Descendants("Item") _
Let fields = items.Descendants("Field") _
From field In fields _
Where field.Attribute("Name").Value = "ID" _
Select field.Value
On a side note: For future reference, if you run into C# anonymous type "var" in examples, the equivalent in vb is plain dim like in my query above (without the 'as type' part).
Hope this helps.
Maverik
Use XPath and save everyone the headaches?
XmlDocument xml = new XmlDocument();
xml.Load(xmlSource);
string id = xml.SelectSingleNode("/Response/Item/Field[#Name='ID']").InnerText;
string name = xml.SelectSingleNode("/Response/Item/Field[#Name='Name']").InnerText;
string path = xml.SelectSingleNode("/Response/Item/Field[#Name='Path']").InnerText;
I am wanting to get the attribute values for ID, Name, and Path.
If you don't mind using something else than XDocument i'd just use a XmlDocument:
XmlDocument doc = new XmlDocument();
doc.Load(new XmlTextReader("XData.xml"));
XmlNodeList items = doc.GetElementsByTagName("Item");
foreach (XmlElement item in items.Cast<XmlElement>())
{
XmlElement[] fields = item.GetElementsByTagName("Field").Cast<XmlElement>().ToArray();
string id = (from s in fields where s.Attributes["Name"].InnerText == "ID" select s).First().InnerText;
string name = (from s in fields where s.Attributes["Name"].InnerText == "Name" select s).First().InnerText;
string path = (from s in fields where s.Attributes["Name"].InnerText == "Path" select s).First().InnerText;
//Do stuff with data.
}
Performance-wise this might be abysmal. You could also have a loop on the Fields and then use a switch on the Name-Attribute so you don't check the same field more than once. Why would you need any linq for this anyway?
XmlDocument doc = new XmlDocument();
doc.Load(new XmlTextReader("XData.xml"));
XmlNodeList items = doc.GetElementsByTagName("Item");
foreach (XmlElement item in items.Cast<XmlElement>())
{
foreach (XmlNode field in item.GetElementsByTagName("Field"))
{
string name = field.Attributes["Name"].InnerText;
switch (name)
{
case "ID":
string id = field.InnerText;
//Do stuff with data.
break;
case "Path":
string path = field.InnerText;
//Do stuff with data.
break;
case "Name":
string name = field.InnerText;
//Do stuff with data.
break;
default:
break;
}
}
}
Your linq query returns all the Item elements in the document:
Dim name = From c In loaded.Descendants("Item") Select c
The code that follows is trying to obtain an 'ID' attribute from the 'Item' element:
Dim str1 = result.Attribute("ID").Value
However, the 'ID' attribute is on a 'Field' child element.
What you need is the following:
// find all the Item elements
var items = loaded.Descendants("Item");
foreach(var item in items)
{
// find all the Field child elements
var fields = item.Descendants("Field");
// find the field element which has an ID attribute, and obtain the element value
string id = fields.Where(field => field.Attribute("ID")!=null)
.Single()
.Value;
// etc ...
}
A Simple solution is
var result = doc.Root.Descendants(XName.Get("Item")).Select(x => x.Descendants(XName.Get("Field")));
foreach (var v in result)
{
string id = v.Single(x => x.Attribute(XName.Get("Name")).Value == "ID").Value;
string name = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Name").Value;
string path = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Path").Value;
string type = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Type").Value;
}
It can be easily converted in to vb code.
Here is a generic solution that handles all fields with different field names in several items. It saves the result in one table containing all distinct field names as column names.
Module Module1
Function createRow(ByVal table As DataTable, ByVal item As XElement) As DataRow
Dim row As DataRow = table.NewRow
Dim fields = item.Descendants("Field")
For Each field In fields
row.SetField(field.Attribute("Name").Value, field.Value)
Next
Return row
End Function
Sub Main()
Dim doc = XDocument.Load("XMLFile1.xml")
Dim items = doc.Descendants("Item")
Dim columnNames = From attr In items.Descendants("Field").Attributes("Name") Select attr.Value
Dim columns = From name In columnNames.Distinct() Select New DataColumn(name)
Dim dataSet As DataSet = New DataSet()
Dim table As DataTable = New DataTable()
dataSet.Tables.Add(table)
table.Columns.AddRange(columns.ToArray())
Dim rows = From item In items Select createRow(table, item)
For Each row In rows
table.Rows.Add(row)
Next
' TODO Handle Table
End Sub
End Module
I tried to use as much Linq as possible, but Linq is a bit inflexible when it comes to handling nested elements recursively.
Heres the sample xml file I've used:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
<Item>
<Field Name="ID">767147519</Field>
<Field Name="Name">Music</Field>
<Field Name="Path">Family\Music</Field>
<Field Name="Type">Playlist</Field>
</Item>
<Item>
<Field Name="ID">123</Field>
<Field Name="Name">ABC</Field>
<Field Name="RandomFieldName">Other Value</Field>
<Field Name="Type">FooBar</Field>
</Item>
</Response>
And the result:
ID Name Path Type RandomFieldName
767147519 Music Family\Music Playlist
123 ABC FooBar Other Value
After some further research and with the assistance of parts from the answers provided, I have come up with the following, which returns the information that I am after.
Dim Query = From items In loaded.Descendants("Item") _
Let sID = ( From q In items.Descendants("Field") _
Where q.Attribute("Name").Value = "ID" ) _
Let sName = ( From r In items.Descendants("Field") _
Where r.Attribute("Name").Value = "Name" ) _
Let sPath = ( From s In items.Descendants("Field") _
Where s.Attribute("Name").Value = "Path" ) _
Where (Ctype(sPath.Value,String) Like "Family\*") _
Select pId=sID.Value, pName=sName.Value, pPath = sPath.Value
If this can be improved in any way to enable better performance, please let me know.
Thank you all for your assistance, while no one answer was able to entirely solve the problem I was able to learn a great deal about Linq through everyones assistance.
Matt
I hope you expected something like this short answer and not another implementation:
Dim items = From c In loaded.Descendants("Item") Select c (...)
Ok so far nothing should run into any trouble. The variable name 'name' was a bit confusing, so I changed it to 'items'.
The second part contains the error:
Dim items = (...) Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
The following works because there is an Attribute called Name, although the result is 'ID' what shurely wasn't expected:
c.Element("Field").Attribute("Name").Value
Here comes the error:
c.Attribute("ID").Value.FirstOrDefault
c is the XmlNode '< Item > ... < / Item >' and it does not have any attributes, thus the result of c.Attribute("ID") is null.
I guess you wanted something like the following:
Dim loaded = XDocument.Load("XMLFile1.xml")
Dim items = From item In loaded.Descendants("Item") Select _
sID = (From field In item.Descendants("Field") _
Where field.Attribute("Name") = "ID" _
Select field.Value).FirstOrDefault() _
, _
sName = (From field In item.Descendants("Field") _
Where field.Attribute("Name") = "Name" _
Select field.Value).FirstOrDefault()
There are a few errors in your code:
You should get the Descendents that have the XName equal to Field instead of to Item
Dim name = From c In loaded.Descendants("Field") Select c
The attribute you are after is called Name, not ID
Dim str1 = result.Attribute("Name").Value
At the first iteration of your for each str1 will be "ID", the next one it will be "Name", etc.
Total code:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Field") Select c
For Each result In name
Dim str1 = result.Attribute("Name").Value 'Returns "ID"
Dim str2 = result.Value ' Returns "767147519"
Next
There's another way to fix this problem. Transform this XML into the format that the DataSet wants, and then load it using DataSet.ReadXml. This is something of a pain if you don't know XSLT. But it's really important to know XSLT if you work with XML.
The XSLT you'd need is pretty simple. Start with the XSLT identity transform. Then add a template that transforms the Response and Item elements into the format that the DataSet expects:
<xsl:template match="Response">
<MyDataSetName>
<xsl:apply-templates select="Item"/>
</MyDataSetName>
</xsl:template>
<xsl:template match="Item">
<MyDataTableName>
<xsl:apply-templates select="Field[#Name='ID' or #Name='Name' or #Name='Path']"/>
</MyDataTableName>
</xsl:template>
<xsl:template match="Field">
<xsl:element name="{#Name}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
That will change your XML to a document that looks like this:
<MyDataSetName>
<MyDataTableName>
<ID>767147519</ID>
<Name>Music</Name>
<Path>Family\Music</Path>
</MyDataTableName>
</MyDataSetName>
...and you can just feed that to DataSet.ReadXml.
Edit:
I should point out, since it's not obvious unless you do this a lot, that one effect of this is that the amount of C# code that you need to create and populate the DataSet is minimal:
private DataSet GetDataSet(string inputFilename, string transformFilename)
{
StringBuilder sb = new StringBuilder();
using (XmlReader xr = XmlReader.Create(inputFilename))
using (XmlWriter xw = XmlWriter.Create(new StringWriter(sb)))
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(transformFilename);
xslt.Transform(xr, xw);
}
using (StringReader sr = new StringReader(sb.ToString()))
{
DataSet ds = new DataSet();
ds.ReadXml(sr);
return ds;
}
}
It's also reusable. You can use this method to populate as many different DataSets from as many different possible input formats as you need; you just need to write a transform for each format.

Resources