Dictionary<string, List<ClassObj>> LINQ To XML Query - c#

I try to put my XML File into a Dictionary with Generic List. How I can merge correct the query List to my dictionary with correct key? Instead of .ToList() .ToDictionary is not possible?
<?xml version="1.0"?>
<customers>
<cust ID="1" DeviceID="1" Name="Bob" Latitude="10" Longitude="58" Device="HW1.0"> </cust>
<cust ID="2" DeviceID="2" Name="Jack" Latitude="28" Longitude="20" Device="HW2.2"> </cust>
</customers>
//XML attribute Name is Dict Key
public class Customers
{
public int Longitude { get; set; }
public int Latitude { get; set; }
public int DeviceID { get; set; }
public string Device { get; set; }
}
class Program
{
private static Dictionary<string, List<Customers>> ReadXmlToDict(string filename)
{
// Should be Key = Xml Attribute Name Value, List of class
Dictionary<string, List<Customers>> dict = new Dictionary<string, List<Customers>>();
XDocument xdoc = XDocument.Load(filename);
var querylist = (from row in xdoc.Descendants("cust")
select new Customers()
{
//Name = (string)row.Attribute("Name"), // Wrong here should be the Dic key
DeviceID = (int)row.Attribute("DeviceID"), // list value
Longitude = (int)row.Attribute("Longitude"), // list value
Latitude = (int)row.Attribute("Latitude"), // list value
Device = (string)row.Attribute("Device") // list value
}).ToList();
return null; // null for test To.List and not Dict
}

This is how I would implement it, I think it accomplishes what you're looking for. You have a class called Customers and then want to store a list of those customers with a single key...I don't follow that logic.
I created a class called Customer, which houses the information for a singular customer. Since you're returning a Dictionary<string, Customer>, where the string is the unique attribute Name in the xml, there is no use case for the value of your dictionary being a List<Customer>. Perhaps if you have multiple customers under the same name, you would use this, but why not then make the key the (I assume) truly unique identifier, the DeviceID?
namespace TestConsole
{
class Customer
{
public int DeviceID;
public int Longitude;
public int Latitude;
public string Device;
}
class Program
{
private static Dictionary<string, Customer> ReadXmlToDictionary(string filename)
{
var dict = new Dictionary<string, Customer>();
var doc = XDocument.Load(#"C:\test.xml");
dict = doc.Descendants("cust")
.ToDictionary(
row => (string)row.Attribute("Name"),
row => new Customer {
Device = (string)row.Attribute("Device"),
DeviceID = (int)row.Attribute("DeviceID"),
Latitude = (int)row.Attribute("Latitude"),
Longitude = (int)row.Attribute("Longitude")
});
return dict;
}
static void Main(string[] args)
{
ReadXmlToDictionary(null);
}
}
}
EDIT: Thought the performance related answer was interesting, so decided to try it out for this single level xml (using ID as the unique identifier). Here are the results:
1019 Descendants took 0.0030257 seconds.
1019 Elements took 0.0028348 seconds.
10000 Descendants took 0.0098942 seconds.
10000 Elements took 0.0101478 seconds.
100000 Descendants took 0.0873025 seconds.
100000 Elements took 0.1223577 seconds.
EDIT:
After creating your xsd, and generating a class from it, you would then use it as such:
var parsed = XDocument.Parse(doc.ToString());
var serializer = new XmlSerializer(typeof(Xsds.customers));
var typedPayload = serializer.Deserialize(doc.Root.CreateReader());
var xmlAsClass = (TestConsole.Xsds.customers)typedPayload;
dict = xmlAsClass.cust
.ToDictionary(
row => (int)row.ID,
row => new Customer {
Device = row.Device,
DeviceID = row.DeviceID,
Latitude = row.Latitude,
Longitude = row.Longitude,
Name = row.Name
});

You can do it easily by using ToDictionary() extension method. But performance wise, it is far better to use Elements() method rather than Descendants(); For further reading please read this blog post:
WHY (OR WHEN) YOU SHOULD/SHOULDN’T USE DESCENDANTS() METHOD
And your query will look like this:
var customersDictionary =
xDoc.Root
.Elements("cust")
.ToDictionary(xe =>
xe.Attribute("Name").Value, xe =>
new Customers
{
DeviceID = (int)xe.Attribute("DeviceID"),
Longitude = (int)xe.Attribute("Longitude"),
Latitude = (int)xe.Attribute("Latitude"),
Device = (string)xe.Attribute("Device")
});

Related

sending key-pair array object using ajax in c#

I am trying to send a key-pair based array object using ajax, here array is created dynamically
["{\"key\":\"#c1\",\"value\":\"g1\"}","{\"key\":\"#c1\",\"value\":\"g2\"}", "{\"key\":\"#c2\",\"value\":\"g3\"}", "{\"key\":\"#c4\",\"value\":\"g4\"}"]
Above is json formatted data which i am sending to a method and able to receive it. At c# end Dictionary<string, string> Columns is used. problem here is that the key values are just number and values contain each element of above mentioned json data as shown below,
foreach(var eachVals in columns)
{
string k = eachVals.Key;
string col = eachVals.Value;
}
when iterating the dictionary eachVals.key is array index (0,1,...) and eachVals.Value contains {"key":"#c1","value":"g1"}
So what i want is separate keys i.e "#c1","#c1","#c2"... and values i.e "g1","g2"...
You would need to deserialize the values from col. Using Newtonsoft.Json, would look something like this:
Dictionary<string, string> columns = new Dictionary<string, string>
{
{ #"0", #"{""key"": ""#c1"", ""value"":""g1"" }" },
{ #"1", #"{""key"": ""#c2"", ""value"":""g2"" }" }
};
var result = columns.ToDictionary(
column => JsonConvert.DeserializeObject<MyObj>(column.Value).key,
column => JsonConvert.DeserializeObject<MyObj>(column.Value).value);
Where MyObj is:
internal class MyObj
{
public string key { get; set; }
public string value { get; set; }
}
I think you can do or get the idea from this:
foreach(var eachVals in columns)
{
var e = eachVals.Value.Split(',');
e = e[0].Split(':');
string k = e[0];
string v = e[1];
}

Issue displaying LINQ query results in a grid in C#

I received some help here with the following LINQ query, but am still struggling with it. The result I'm trying to obtain is to display some attributes and their values from an xml file in a DataGridView control. I'm calling my method from a button click and am trying to pass back the list for display in the grid. Here is an example of the row:
<z:row CenterCode="JAX" CenterName="Jacksonville" Version="1.0" NextExport="66742" NextImport="29756" LastImportTime="2015-06-10T14:48:33" FtpProxyServer="" FtpUserName="" FtpPassword="" ResetImportID="False"/>
Here is the method:
public static List<string[]> MonitorCounts(string upperLimit)
{
// Load xml
XDocument xmldoc = XDocument.Load(#"c:\XML\Configuration.xml");
XNamespace z = "#RowsetSchema";
Int32 limit = Convert.ToInt32(upperLimit);
var elementQuery = xmldoc.Descendants(z + "row").Where(e => (long?)e.Attribute("NextExport") > limit | (long?)e.Attribute("NextImport") > limit);
var attributes = elementQuery.Select(e => e.Attributes().Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
return attributes;
}
My questions are how to select only specific attributes and values in attributes. If I do something like this:
var attributes = elementQuery.Select(e => e.Attributes("CenterName").Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
then this is returned:
[0] = {[CenterName, Jacksonville]}
I need to select this and 4 others. I'm also getting a convrsion error - Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>>>' to 'System.Collections.Generic.List<string[]>. Appreciate any pointers to help me along.
You can use an anonymous type:
var attributes =
elementQuery.Select(e => new
{
CenterName = (string)e.Attribute["CenterName"],
Version = (string)e.Attribute["Version"],
// more attributes
}).ToList();
You can't however return this from the method in a useful way. So if you really need both the attribute name and the attribute value as strings, try this approach instead:
var attributes =
elementQuery.Select(e => new []
{
Tuple.Create("CenterName", (string)e.Attribute["CenterName"]),
Tuple.Create("Version", (string)e.Attribute["Version"]),
// more attributes
}).SelectMany(x => x).ToList();
The return type of your method now has to be List<Tuple<string, string>>.
And finally, if you actually need a List<string[]> as the return type, use this code:
var attributes =
elementQuery.Select(e => new []
{
new [] { "CenterName", (string)e.Attribute["CenterName"] },
new [] { "Version", (string)e.Attribute["Version"] },
// more attributes
}).SelectMany(x => x).ToList();
I solved my own problem. Here is what I did:
Created a class for the attributes needed:
public class dataRow
{
public string CenterName { get; set; }
public string CenterCode { get; set; }
public string NextImport { get; set; }
public string NextExport { get; set; }
public string LastImportTime { get; set; }
}
Selected the results into it:
List<dataRow> dataRows = elementQuery.Select( e => new dataRow
{ CenterName = (string)e.Attribute("CenterName"),
CenterCode = (string)e.Attribute("CenterCode"),
NextImport = (string)e.Attribute("NextImport"),
NextExport = (string)e.Attribute("NextExport"),
LastImportTime = (string)e.Attribute("LastImportTime") }).ToList();
Changed my method to return the correct object:
public static List<dataRow> MonitorCounts(string upperLimit)
Set my grids datasource to the method return:
dataGridView1.DataSource = xmlProcessing.MonitorCounts(tbxUpperLimit.Text.ToString());
return dataRows;

How to group by records in c#

I am trying to use Group By method supported by LINQ.
I have this class
public class Attribute
{
public int Id {get;set;}
public string Name {get;set;}
public string Value {get;set;}
}
I have a service method that will retrive a IList
var attributes = _service.GetAll();
Id Name Value
7 Color Black
7 Color White
220 Size 16
Now I have another tow classes
one is
public class AttributeResourceModelSubItem
{
public string Name {get;set;}
public List<AttributeValueResourceModelSubItem> values { get; set; }
}
public class AttributeValueResourceModelSubItem
{
public int Id;
public string Name {get;set;}
}
I am trying to loop through the attributes list. and if the attribute id is the same, I wanna insert the records where id = to that id inside the AttributeValueResourceModelSubItem in which id = 1 and Name will be equal to the attribute value.
This what I got so far.
private IList<AttributeResourceModelSubItem> FormatAttributes(IList<Attribute> attributes)
{
Dictionary<int, Attribute> baseTypes = new Dictionary<int, Attribute>();
AttributeResourceModelSubItem attributeResourceModelSubItem = null;
var list = new IList<AttributeResourceModelSubItem>();
foreach (var item in attributes)
{
if (!baseTypes.ContainsKey(item.Id))
{
attributeResourceModelSubItem = new AttributeResourceModelSubItem()
attributeResourceModelSubItem.key = item.Name;
attributeResourceModelSubItem.values.Add(new AttributeValueResourceModelSubItem()
{
id = 1,
name = item.Value
});
list.Add(attributeResourceModelSubItem);
}
baseTypes.Add(item.Id, item);
}
return list;
}
Any help is appreciated.
It's pretty unclear from your example what you're actually trying to do, but this is the gist I get.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.Id)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.First().Name,
values = c.Select(x => new AttributeValueResourceModelSubItem()
{
id = 1,
name = x.value
}).ToList();
});
}
You should also definitely not use the word Attribute as a class name. That's already a .NET class.
I'll admit that I don't quite understand the id = 1 part, but I took that from your code. It also seems odd to group by the id then try and take the first name, but again that's what you have.
If you do, in fact, want to group by the name and take the id, which makes a little more sense, you'll want to swap a couple things around. Admittedly this structure still seems a little odd to me, but hopefully this will get you a couple steps closer to your goal.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.name)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.Key,
values = c.Select((item, index) => new AttributeValueResourceModelSubItem()
{
id = index + 1,
name = item.value
}).ToList();
});
}
I also made your id = 1 increment starting at one for each element in each values list. You might want that to be item.Id, or even just your original 1.

How to elegantly parse the following text into a dictionary

I have the following text that I need to put into a dictionary. At first sight I thought that it would be very easy but at the end I found myself doing extensive string search and finding sometimes values that break the parser.
"0":
{
"key":"valueWithAnyCharInside",
"key2":"valueWithAnyCharInside",
"key3":"valueWithAnyCharInside"
},
This will map into the following model:
class Item
{
private int id;
private Dictionary<string, string> data;
}
Any ideas? Maybe using regex ...
Your data format is probably a JSON, but you gave only a part of it. I've modified it slightly as:
{"0":
{
"key":"valueWithAnyCharInside",
"key2":"valueWithAnyCharInside",
"key3":"valueWithAnyCharInside"
}
}
now you can parse it as following:
string json = ...; //your json goes here
var serializer = new JavaScriptSerializer();
var parsed = serializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(json);
//printing data
parsed["0"].Select(pair => string.Format( "{0} - {1}", pair.Key, pair.Value))
.ToList()
.ForEach(Console.WriteLine);
prints:
key - valueWithAnyCharInside
key2 - valueWithAnyCharInside
key3 - valueWithAnyCharInside
To get strongly typed List<Item> use next code
List<Item> items = parsed.Select(pair => new Item { Id = int.Parse(pair.Key),
Data = pair.Value})
.ToList();
Where Item is :
class Item
{
public int Id { get; set; }
public Dictionary<string, string> Data {get;set;}
}

creating composite objects with linq to xml

I have a simple xml doc I am reading, a sample is here:
<people>
<person>
<name>joe</name>
<age>21</age>
<contact>
<phone-nums>
<phone-num>
<number>123-4567</number>
<type>home</type>
</phone-num>
<phone-num>
<number>123-4567</number>
<type>office</type>
</phone-num>
</phone-nums>
</contact>
</person>
</people>
I read it in using HttpContent.ReadAsXElement() and then use Linq to create objects. My simple objects look something like this:
public class PeopleList : List<Person> { }
public class Person
{
public string name;
public int age;
public Contact contact;
}
public class Contact
{
public PhoneList phones;
}
public class PhoneList : List<Phone>{}
public class Phone
{
public string number;
public string type;
}
Ok, so now I have my class that reads it all in which is where I am getting hung up (it's an extension method in my code):
public PeopleList ReadAsPeopleList(this HttpContent content)
{
var people = content.ReadAsXElement();
var personQuery = from p in people.Elements("person")
select new Person()
{
name = p.Element("name").ValueOrDefault(),
age = p.Element("age").ValueOrDefault(),
contact = (from c in p.Elements("contact")
select new Contact()
{
//I don't know how to select a new list of phones into a contact here
}
};
PeopleList l = new PeopleList();
l.AddRange(personQuery);
return l;
}
I'm having trouble creating the contact type with the composite phone number list. Any help would be appreciated.
Note: I rewrote a simplified version of all of this here so
To get the collection of 'Phone' that needs to go in the contact, you could use this:
c.Elements("phone-num").Select(phone => new Phone()
{
number = phone.Element("number").Value,
type = phone.Element("type").Value
});
so you want
select new Contact()
{
PhoneList = c.Elements("phone-num").Select(phone => new Phone()
{
number = phone.Element("number").Value,
type = phone.Element("type").Value
})
}
This answer will be a bit skewed from your actual question, but may provide some direction to your eventual solution.
Consider using a List<T> for your collections rather than creating a BusinessObjectCollection : List<T>. Here's a good SO read that may be of interest: List or BusinessObjectCollection?
With that being said, this is a somewhat tweaked version of your classes; I've used properties instead of fields as well. And finally, since I've not worked with HTTPContext much, I thew together an example using a basic string. The method presented here should be easy enough to convert into an extension method for HTTPContext, though:
public static IEnumerable<Person> ReadAsPeopleList( string xml )
{
var doc = XDocument.Parse( xml );
var people = doc.Root.Elements( "person" )
.Select( x => new Person
{
Name = x.Element( "name" ).Value,
Age = int.Parse( x.Element( "age" ).Value ),
Contact = new Contact
{
Phones = x.Descendants( "phone-num" )
.Select( p => new Phone
{
Number = p.Element( "number" ).Value,
Type = p.Element( "type" ).Value
} )
}
}
);
return people;
}
private static string MyXml = #"
<people><person><name>joe</name><age>21</age><contact><phone-nums>
<phone-num><number>123-4567</number><type>home</type></phone-num>
<phone-num><number>123-4567</number><type>office</type></phone-num>
</phone-nums></contact></person><person><name>bill</name><age>30</age>
<contact><phone-nums><phone-num><number>123-4567</number><type>home</type>
</phone-num><phone-num><number>123-4567</number><type>office</type>
</phone-num></phone-nums></contact></person></people>";

Categories

Resources