creating composite objects with linq to xml - c#

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>";

Related

sort custom array list with a specific field

i would like to sort by postal address but i am unable to i have seen some Linq functions tried them but i can't seem to get all the required parameters needed.
for example i saw this one example
list.Sort((p, q) => p.Category.CompareTo(q.Category)); /*has and error that says cannot convert lamba expressions to type '|Comparer' because it is not a delegate*/
but i dont seem to understand how to use it.
MyCustomList.cs
class MyCustomList
{
private string name;
private string postalAddress;
public MyCustomList(string name, string postalAddress)
{
this.name = name;
this.postalAddress = postalAddress;
}
//getters and setters
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public string PostalAddress
{
get
{
return postalAddress;
}
set
{
postalAddress = value;
}
}
}
Form1.cs
ArrayList list = new ArrayList();
list.Add(new MyCustomList("A somename","A Fake Postal Address");
list.Add(new MyCustomList("B somename","B Fake Postal Address");
list.Sort(); // Sort by Postal adress
Do you really need to use ArrayList?
It's a relic from the pre-generics days of .NET, and you should really be using an implementation of IEnumerable<T> where possible e.g. List<T>.
LINQ operates on IEnumerable<T>, so won't work with your ArrayList, and the method you are looking for is OrderBy or OrderByDescending.
Example:
var list = new List<MyCustomList>();
list.Add(new MyCustomList("A somename","A Fake Postal Address"));
list.Add(new MyCustomList("B somename","B Fake Postal Address"));
list.OrderBy(cl => cl.Postcode); // Sort by Postal address
First stop using ArrayList - its as good as obsolete.
Either using Array like this
var list = MyCustomList[2];
list[0] = new MyCustomList(...
list[1] = new MyCustomList(....
or use something like the List<T> class
var list = new List<MyCustomList>();
list.Add(new MyCustomList(...
list.Add(new MyCustomList(...
If you use array then the Sort function that takes an instance of Comparison<T> is static
see the documentation here https://learn.microsoft.com/en-us/dotnet/api/system.array.sort?view=netframework-4.8#System_Array_Sort__1___0___System_Comparison___0__
you need to call it like so:
Array.Sort(list, (a,b) => a.PostalAddress.CompareTo(b.PostalAddress))
or use linq on your List or Array and use OrderBy
var orderedList = list.OrderBy(a => a.PostalAddress);
Already approved by many https://stackoverflow.com/a/57371579/6923146
For order wise sorting with a specific field in c# using linq
list = list.OrderByDescending(x => x.Name).ToList();
list = list.OrderBy(x => x.Name).ToList();
//list.OrderBy(x => x.YourClassSpecificField).ToList()
Example:
please try to run following code in fiddle :
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<MyCustomList> list = new List<MyCustomList>();
list.Add(new MyCustomList("A somename", "A Fake Postal Address"));
list.Add(new MyCustomList("B somename", "B Fake Postal Address"));
//list.Sort();
Console.WriteLine("descending order");
list = list.OrderByDescending(x => x.Name).ToList();
foreach (MyCustomList o in list)
{
Console.WriteLine(o.Name + " -- " + o.PostalAddress );
}
Console.WriteLine("ascending order");
list = list.OrderBy(x => x.Name).ToList();
foreach (MyCustomList o in list)
{
Console.WriteLine(o.Name + " -- " + o.PostalAddress );
}
}
public class MyCustomList
{
private string name;
private string postalAddress;
public string Name
{
get { return name; }
set { name = value; }
}
public string PostalAddress
{
get { return postalAddress; }
set { postalAddress = value; }
}
public MyCustomList(string name, string postalAddress)
{
this.name = name;
this.postalAddress = postalAddress;
}
}
}

How do I replace words in a string that matches a keywords stored in an array with data from a table?

I want to replace words in a string that matches a keywords stored in an array with data from matching column in a table.
My model is People
public class People()
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The method i have tried is :
public void ProcessString(string message)
{
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
for (int i=0; i<keyword.Length; i++)
{
string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata);
}
}
}
So instead of matchingcolumndata in the string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata); line i want to put the data that is matching the column in my table People. Please help with the question if its not straight to the point.
From my understand it may be you are looking for
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
body= body.Replace(keyword[0],person.Title);
body= body.Replace(keyword[1],person.Name);
body= body.Replace(keyword[2],person.Surname);
}
It doesn't make any sense to declare your keywords array inside the loop. If you want to do this in a neat way (and subject to modifications easily), you can create a map between the keyword and the property of the Person. Like this:
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
// maps each keyword to a property in the Person class
var keywordPropertyMapping = new Dictionary<string, Func<Person, string>>()
{
{ "#Title", p => p.Title },
{ "#Name", p => p.Name },
{ "#Surname", p => p.Surname }
};
foreach (var person in people)
{
foreach(var keywordFunc in keywordPropertyMapping)
{
body = body.Replace(keywordFunc.Key, keywordFunc.Value(people));
}
}
}
For any new keyword, you just add one simple line inside the dictioanry keywordPropertyMapping, and the magic works.
If you're not familiar with Func<T, TResult> which is one critical part of the Linq magic, then read the Docs:
Encapsulates a method that has one parameter and returns a value of
the type specified by the TResult parameter.
You can use this delegate to represent a method that can be passed as
a parameter without explicitly declaring a custom delegate. The
encapsulated method must correspond to the method signature that is
defined by this delegate. This means that the encapsulated method must
have one parameter that is passed to it by value, and that it must
return a value.

Dictionary<string, List<ClassObj>> LINQ To XML Query

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")
});

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.

get the object properties

I have a code that returns me an object of widgets
XDocument loaded = XDocument.Parse( xml );
var widgets = from x in loaded.Descendants( "widget" )
select new
{
URL = x.Descendants( "url" ).First().Value,
Category = x.Descendants( "PortalCategoryId" ).First().Value
};
I am trying to create a method that will return the object widgets and then I need another method where I can cal it from and access the values. I am new to C# and using vs2010
thanks
Anonymous types cannot easily be shared across methods.
You should make a class to store that data.
Instead of
XDocument loaded = XDocument.Parse( xml );
var widgets = from x in loaded.Descendants("widget")
select new // Dynamic/Anonymous class
{
URL = x.Descendants( "url" ).First().Value,
Category = x.Descendants( "PortalCategoryId" ).First().Value
};
It would be better to create a concreate class
//Widget.cs
Public class Widget
{
public string URL { get; set; }
public string Category { get; set; }
}
//Code somewhere else..
XDocument loaded = XDocument.Parse(xml);
IEnumerable<Widget> widgets =
from x in loaded.Descendants("widget")
select new Widget()
{
URL = x.Descendants( "url" ).First().Value,
Category = x.Descendants( "PortalCategoryId" ).First().Value
};
change var widgets to dynamic widgets
example
using System;
using System.Linq;
class Sample {
static object junk(){
var widgets = new { URL = new Uri("http://test.com/"), Category = "address" };
return widgets;
}
static void Main(){
dynamic widgets = junk();//var widgets = .. //NG
Console.WriteLine(widgets.URL);
}
}

Categories

Resources