I'm looking to parse a relatively complex XML file through C# and store a selection of the data into a SQL Server '08 database. This is what I'm looking to extract from the XML file:
<educationSystem>
<school>
<name>Primary School</name>
<students>
<student id="123456789">
<name>Steve Jobs</name>
<other elements>More Data</other elements>
</student>
<student id="987654">
<name>Jony Ive</name>
<otherElements>More Data</otherElements>
</student>
</students>
</school>
<school>
<name>High School</name>
<students>
<student id="123456">
<name>Bill Gates</name>
<other elements>More Data</other elements>
</student>
<student id="987654">
<name>Steve Ballmer</name>
<otherElements>More Data</otherElements>
</student>
</students>
</school>
</educationSystem>
[Before you ask, no this isn't a school assignment - I'm using school/students as an example and because the original is a lot more sensitive.]
I'm able to (using XDocument/XElement) parse the XML file and get a list of all school names, student names and student ID's, but when this gets added to the database, I end up with the Bill Gates student entry being under a second school. It's all just line-by-line.
I'm looking to find a way to say, achieve this:
Foreach school
put it's name into an XElement
foreach student
grab the name and id put into XElements
Grab next school and repeat
I believe Linq would be the best way to achieve this, but I'm having trouble in how to get started with the process. Would anyone be able to point me in the right direction?
Edit: Here's the code I'm currently using to save data to the database. It processes a list at a time (hence things aren't related as they should be). I'll also be tidying up the SQL as well.
private void saveToDatabase (List<XElement> currentSet, String dataName)
{
SqlConnection connection = null;
try
{
string connectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString + "; Asynchronous Processing=true";
connection = new SqlConnection(connectionString);
connection.Open();
foreach (XElement node in currentSet)
{
SqlCommand sqlCmd = new SqlCommand("INSERT INTO dbo.DatabaseName (" + dataName + ") VALUES ('" + node.Value + "')", connection);
sqlCmd.ExecuteNonQuery();
}
}
This LINQ will generate a Collection of Objects,with two properties
Name of the school
List of students(again a collection)
var result = XElement.Load("data.xml")
.Descendants("school")
.Select( x => new {
name = XElement.Parse(x.FirstNode.ToString()).Value,
students =x.Descendants("student")
.Select(stud => new {
id = stud.Attribute("id"),
name = XElement.Parse(stud.FirstNode.ToString()).Value})
.ToList()});
Note:The LINQ assumes <name> as the first node under <school> and <student> tags
Then you can use the foreach that you intended and it will work like a charm
foreach (var school in result)
{
var schoolName = school.name;
foreach (var student in school.students)
{
//Access student.id and student.name here
}
}
For this particular type of workings with XML data, you could use XML Serialization / Deserialization.
This will allow you to Deserialize your XML Data into a IEnumerable Class Object, Perform your LINQ Queries on this Class and then save to SQL.
Hope this helps.
Update: The original code example did not mention a namespace. Namespaces need to be either accounted for when searching for elements by XName or one needs to to search using the XName.LocalName property. Updated the example to show how to handle selecting elements in such a case.
namespace Stackover
{
using System;
using System.Xml.Linq;
class Program
{
private const string Xml = #"<?xml version=""1.0"" encoding=""UTF-8""?>
<namespaceDocument xmlns=""http://www.namedspace/schemas"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:schemaLocation=""http://www.namedspace/schemas.xsd"">
<educationSystem>
<school>
<name>Primary School</name>
<students>
<student id=""123456789"">
<name>Steve Jobs</name>
<otherElements>
<dataA>data</dataA>
</otherElements>
</student>
<student id=""987654"">
<name>Jony Ive</name>
<otherElements>
<dataB>data</dataB>
</otherElements>
</student>
</students>
</school>
<school>
<name>High School</name>
<students>
<student id=""123456"">
<name>Bill Gates</name>
<otherElements>
<dataC>data</dataC>
</otherElements>
</student>
<student id=""987654"">
<name>Steve Ballmer</name>
<otherElements>
<dataD>data</dataD>
</otherElements>
</student>
</students>
</school>
</educationSystem>
</namespaceDocument>";
static void Main(string[] args)
{
var root = XElement.Parse(Xml);
XNamespace ns = "http://www.namedspace/schemas";
foreach(var school in root.Descendants(ns + "school")) // or root.Descendants().Where(e => e.Name.LocalName.Equals("school"));
{
Console.WriteLine(school.Element(ns + "name").Value);
foreach (var students in school.Elements(ns+ "students"))
{
foreach (var student in students.Elements())
{
Console.WriteLine(student.Attribute("id"));
Console.WriteLine(student.Name); // Name = namespace + XName
Console.WriteLine(student.Name.LocalName); // no namespace
}
}
}
}
}
}
Related
I have a collection string with information about customers like name, gender etc. All custumers have an id.
Now i want to create a common XML file with all customers in it. Something like example below:
<custumers>
<custumer>
<name></name>
<id></id>
<etc></etc>
</custumer>
</custumers>
The start up with no XML file is easy, I used linq to create the xml file.
For initial creation I used following code:
try
{
var xEle = new XElement("Customers",
from cus in cusList
select new XElement("Customer",
new XElement("Name", cus.Name),
new XElement("gender", cus.gender),
new XElement("etc", cus.etc));
}
xEle.Save(path);
But on the point if i want to update the XML file i get some problems to get it.
My approach to solve it:
Iterate over all customers in list and check for all customers if the customer.id exists in the XML.
IF not: add new customer to xml
IF yes: update values
My code so far:
var xEle = XDocument.Load(xmlfile);
foreach (cus in cusList)
try
{
var cids = from cid in xEle.Descendants("ID")
where Int32.Parse(xid.Element("ID").Value) == cus.ID
select new XElement("customer", cus.name),
new XElement ("gender"), cus.gender),
new XELement ("etc."), cus.etc)
);
xEle.Save(xmlpath);
}
How about a different approach....
using System.IO;
using System.Text;
using System.Xml.Serialization;
public void Main()
{
DataSet Custumers = new DataSet("Custumers");
DataTable Custumer = new DataTable("Custumer");
// Create the columns
Custumer.Columns.Add("id", typeof(int)).Unique = true;
Custumer.Columns.Add("name", typeof(string));
Custumer.Columns.Add("gender", typeof(string));
Custumer.Columns.Add("etc", typeof(string));
// Set the primary key
Custumer.PrimaryKey = { Custumer.Columns("id") };
// Add table to dataset
Custumers.Tables.Add(Custumer);
Custumers.AcceptChanges();
// Add a couple of rows
Custumer.Rows.Add(1, "John", "male", "whatever");
Custumer.Rows.Add(2, "Jane", "female", "whatever");
Custumer.AcceptChanges();
// Let's save this to compare to the updated version
Custumers.WriteXml("Custumers_Original.xml");
// Read in XML that contains an existing Custumer and adds a new one
// into a Clone of the Custumer table
DataTable CustumerUpdate = Custumer.Clone;
CustumerUpdate.ReadXml("Custumers_Update.xml");
// Merge the clone table data to the Custumer table
Custumer.Merge(CustumerUpdate);
Custumer.AcceptChanges();
Custumers.WriteXml("Custumers_Final.xml");
}
Custumers_Original.xml looks like this:
<?xml version="1.0" standalone="yes"?>
<Custumers>
<Custumer>
<id>1</id>
<name>John</name>
<gender>male</gender>
<etc>whatever</etc>
</Custumer>
<Custumer>
<id>2</id>
<name>Jane</name>
<gender>female</gender>
<etc>whatever</etc>
</Custumer>
</Custumers>
Custumers_Update.xml has this, making a change to John and adding George:
<?xml version="1.0" encoding="utf-8" ?>
<Custumers>
<Custumer>
<name>John</name>
<id>1</id>
<gender>male</gender>
<etc>this is new</etc>
</Custumer>
<Custumer>
<name>George</name>
<id>3</id>
<gender>male</gender>
<etc>grandson</etc>
</Custumer>
</Custumers>
After the merge, the Custumers_Final.xml contains this:
<?xml version="1.0" standalone="yes"?>
<Custumers>
<Custumer>
<id>1</id>
<name>John</name>
<gender>male</gender>
<etc>this is new</etc>
</Custumer>
<Custumer>
<id>2</id>
<name>Jane</name>
<gender>female</gender>
<etc>whatever</etc>
</Custumer>
<Custumer>
<id>3</id>
<name>George</name>
<gender>male</gender>
<etc>grandson</etc>
</Custumer>
</Custumers>
Also my solution with linq:
try
{
XDocument xEle = XDocument.Load(path);
var ids = from id in xEle.Descendants("Custom")
where id.Element("id").Value == cus.ID
select id;
foreach (XElement idCustom in ids)
{
idCustom.SetElementValue("NewName", "NewElement");
}
xEle.Save(path);
}
Is it possible in C# to get XElement of XDocument by giving in
the line number?
Ive got any test XML like:
<Student>
<Name>Josphine</Name>
</Student>
<Student>
<Name>Hendrick</Name>
</Student>
I want to give as Parameter any integer like 5.
5 would give me the Element <Name>Hendrick</Name>
Is this possible in any way? Or do I Need to parse the whole
XDocument by a Reader and check the line number every loop.
You can read your file to string array
string[] lines = File.ReadAllLines("path/to/file");
And then get your line like lines[4].
Or you should better look at XPath as your XML document can change.
Take a look at these exaples and tutorials: XPath Examples, Selecting Nodes.
There is a another look-around, if your XML is well-formed and you want your job get done using XLinq only, then below code might help you:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
XDocument doc = XDocument.Parse(#"<Students>
<Student>
<Name>Josphine</Name>
</Student>
<Student>
<Name>Hendrick</Name>
</Student>
</Students>", LoadOptions.SetLineInfo);
IEnumerable<XElement> descendants = doc.Descendants();
foreach (XElement ele in descendants)
{
string ln_num = (((IXmlLineInfo)ele).HasLineInfo() ? ((IXmlLineInfo)ele).LineNumber.ToString() : "");
string ln_pos = (((IXmlLineInfo)ele).HasLineInfo() ? ((IXmlLineInfo)ele).LinePosition.ToString() : "");
Console.WriteLine(string.Format("{0} ({1}): at line no. {2}, position {3}", ele.Name.ToString(), ele.Value.ToString(), ln_num.ToString(), ln_pos.ToString()));
}
Console.ReadKey();
}
}
}
I am trying to add my code for already existing code. I have below two xmls:
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
In.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="2">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Now I need to check Out.xml and In.xml and my final Out.xml must be like below. The rule here is check StudentID in Out and In xmls. If Out xml doesnot have it and In xml has it add it to Out.xml at end of already existing elements.
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="2">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Already existing code is as below
string inFileName = #"C:\In.xml";
string inXml = System.IO.File.ReadAllText(inFileName);
var xmlReaderSource = XmlReader.Create(new StringReader(inXml));
var mgr = new XmlNamespaceManager(xmlReaderSource.NameTable);
mgr.AddNamespace("m", "http://www.mismo.org/residential/2009/schemas");
XDocument sourceXmlDoc = XDocument.Load(xmlReaderSource);
string outFileName = #"C:\Out.xml";
string outXml = System.IO.File.ReadAllText(outFileName);
XmlDocument targetXmlDoc = new XmlDocument();
targetXmlDoc.LoadXml(outXml);
I cannot change above code now I need add my logic.
I added like below
string xpath = #"/m:School/m:Classes/m:Class/m:Students";
XmlNodeList outStudentNodes = targetXmlDoc.SelectNodes(xpath + "/m:Student", namespaceManager);
if(outStudentNodes== null || outStudentNodes.Count <= 0)
{
return;
}
XElement root = sourceXmlDoc.Root;
IEnumerable<XElement> inStudentsColl = from item in root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student")
select item;
Now I have XmlNodeList and IEnumerble, trying to see whether I can use LINQ statement and make code simple for my comparison.
Node: I am not asking how to add nodes/elements using C#. I am looking for how to compare two xmls and then add nodes/elements into the one which is missing those nodes/elements. My issue here is one xml is read like XDocument and other using XmlDocument.
UPDATE
Thank you very much #TheAnatheme. I really appreciate it.
I followed what TheAnatheme suggested me and it worked. I marked TheAnatheme's answer as real solution. Please see below what I did in foreach block so that if anyone wants to use they can refer to this post.
string xpath = #"/m:School/m:Classes/m:Class/m:Students
XmlNode studentsNode = targetXmlDoc.SelectSingleNode(xpath, namespaceManager);
foreach (var element in elementsToAdd)
{
//Add Microsoft.CSharp.dll (if needed ) to your project for below statement to work
dynamic studentElement = element as dynamic;
if (studentElement != null)
{
XmlElement studentXmlElement = targetXmlDoc.CreateElement("Student");
XmlElement studentIDXmlElement = targetXmlDoc.CreateElement("ID");
studentIDXmlElement.InnerText = studentElement.ID;
XmlElement studentNameXmlElement = targetXmlDoc.CreateElement("Name");
studentNameXmlElement .InnerText = studentElement.Name;
studentXmlElement.AppendChild(studentIDXmlElement);
studentXmlElement.AppendChild(studentNameXmlElement);
studentsNode.AppendChild(childElement);
}
}
This projects both sets into an anonymous object List, makes comparisons, and gives you a set of anonymous objects that don't yet exist by which you can add to the out XML.
public static List<object> GetInStudents(XDocument sourceXmlDoc)
{
IEnumerable<XElement> inStudentsElements =
sourceXmlDoc.Root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student");
return inStudentsElements.Select(i =>
new { Id = i.Elements().First().Value,
Name = i.Elements().Last().Value }).Cast<object>().ToList();
}
public static List<object> GetOutStudents(XmlDocument targetXmlDoc)
{
XmlNodeList outStudentsElements = targetXmlDoc.GetElementsByTagName("Students")[0].ChildNodes;
var outStudentsList = new List<object>();
for (int i = 0; i < outStudentsElements.Count; i++)
{
outStudentsList.Add(new { Id = outStudentsElements[i].ChildNodes[0].InnerText,
Name = outStudentsElements[i].ChildNodes[1].InnerText });
}
return outStudentsList;
}
And you compare them as such:
var inStudents = GetInStudents(sourceXmlDoc);
var outStudents = GetOutStudents(targetXmlDoc);
if (inStudents.SequenceEqual(outStudents))
{
return;
}
else
{
var elementsToAdd = inStudents.Except(outStudents);
foreach (var element in elementsToAdd)
{
// create xmlNode with element properties, add element to xml
}
}
I have an XML data of Employees, I want to parse this data and show it in Gridview using C# in ASP.NET.
Based on search parameters like Employee ID or Company ID or Department ID, I should be able to filter the data and update the GridView.
Checked few links in internet, but nothing matches this particular format.. Is it possible to achieve.. (Any links to) code will be helpful.
<?xml version="1.0" encoding="utf-8"?>
<Employees>
<Employee>
<Id> TG18-2002</Id>
<Name> AAPM^Test^Patterns</Name>
<Sex> O </Sex>
<Company>
<Id> 2.16</Id>
<Department>
<Id> 2.16.124</Id>
<Project>
<Id> 2.16.124.113543</Id>
</Project>
</Department>
</Company>
</Employee>
<Employee>
<ID> TG18-2003</ID>
<Name> AAPM^Test^Patt</Name>
<Sex> O </Sex>
<Company>
<ID> 2.16</ID>
<Department>
<ID> 2.16.124</ID>
<Project>
<ID> 2.16.124.113543</ID>
</Project>
</Department>
</Company>
</Employee>
<Employee>
</Employees>
Note: I am trying to build something like, this
Looking at the link you referenced, I think you're best off creating an Employee class and filling this with the XML data.
This will allow you to de-couple your data (in this case XML, but could be anything) from your view (in this case ASP.NET Web Forms, but again could be anything), which I find handy and seems to be common these days (MVVM etc..).
Another benefit is that you can turn a nested data source like XML into something a little flatter to make your view binding simpler, for example in the example you provided you could make the Company fields available as properties of the outer Employee class.
Yet another benefit is that if/when your view becomes responsive, then you can make properties of your view model observable, and therefore allow updates to the model, immediately update your view.
All that said, here is a small snippet showing your Employee class, and filling it from the XML sample you provided. I am using Linq to XML, but there are so many ways you could do this (adapters, readers, navigators...).
I think the important thing here is that you de-couple your data from the view.
class Employee
{
public string Id { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
public Company Company { get; set; }
}
class Company
{
public string Id { get; set; }
public Department Department { get; set; }
}
class Department
{
public string Id { get; set; }
public Project Project { get; set; }
}
class Project
{
public string Id { get; set; }
}
var xml = #"<?xml version='1.0' encoding='utf-8'?>
<Employees>
<Employee>
<Id> TG18-2002</Id>
<Name> AAPM^Test^Patterns</Name>
<Sex> O </Sex>
<Company>
<Id> 2.16</Id>
<Department>
<Id> 2.16.124</Id>
<Project>
<Id> 2.16.124.113543</Id>
</Project>
</Department>
</Company>
</Employee>
<Employee>
<Id> TG18-2003</Id>
<Name> AAPM^Test^Patt</Name>
<Sex> O </Sex>
<Company>
<Id> 2.16</Id>
<Department>
<Id> 2.16.124</Id>
<Project>
<Id> 2.16.124.113543</Id>
</Project>
</Department>
</Company>
</Employee>
</Employees>
";
// read the xml into the class
var doc = XDocument.Parse(xml);
var data = (from row in doc.Root.Elements("Employee")
let company = row.Element("Company")
let dept = company.Element("Department")
let project = dept.Element("Project")
select new Employee
{
Id = row.Element("Id").Value.Trim(),
Name = row.Element("Name").Value.Trim(),
Sex = row.Element("Sex").Value.Trim(),
Company = new Company
{
Id = company.Element("Id").Value.Trim(),
Department = new Department
{
Id = dept.Element("Id").Value.Trim(),
Project = new Project
{
Id = project.Element("Id").Value.Trim()
}
}
}
});
// now you have a collection of 'employees', bind them..
Once we're that far, you need to decide how you want to query your data. The answer to this is as usual, it depends, and in this case I think it depends mostly on the size of the XML data you have and if it makes sense to bring this into memory or not.
If you can bring it into memory, then a collection of Employees is nice and simple to query using Linq and I would recommend this approach.
If the XML is large, then you will probably need to use an XMLReader to build your class. A little more complex, but the result should still be that you bind your grid to your class and keep the XML separate from the view.
DataSet xmlData = new DataSet();
xmlData.ReadXml(YourXMLPath);
GridviewControl1.DataSource = xmlData.Tables[0];
as far my understanding you need to do maipulication in data before showing it in Grid Control.
for that i recommend you to use LINQ query. you can manipulicate the things at client side with queries.
below i trying to solve the prob.
DataSet xmlData = new DataSet();
xmlData.ReadXml(YourXMLPath);
DataTable dt =From x in xmlData.Tables[0].AsEnumerable().Where(x=>x.Field<string>("Name").StartWith("A")) Selct(x=>x).CopytodDataTable;
now you can use "dt" for your data grid
xmlData.Tables[0]= dt;
GridviewControl1.DataSource = xmlData.Tables[0];
GridviewControl1.DataBind();//this line required if it is for asp.net
Hope it will helps you. :)
I have an sql query, selects some data from a table.
ID Name Number Email
1 a 123 a#a.com
2 b 321 b#b.com
3 c 432 c#c.com
I get these datas from the table. I want create a xml file from the data. Like this
<Students>
<Student>
<id>1</id>
<name>a</name>
<number>123</number>
<email>a#a.com</email>
</Student>
<Student>
<id>2</id>
<name>b</name>
<number>321</number>
<email>b#b.com</email>
</Student>
<Student>
<id>3</id>
<name>c</name>
<number>432</number>
<email>c#c.com</email>
</Student>
</Students>
How can I do it on C# and SQL Server ?
Try this:
SELECT *
FROM dbo.YourStudentTable
FOR XML PATH('Student'), ROOT ('Students')
This should return exactly the XML you're looking for (assuming you have SQL Server 2005 or newer)
Read more about how to use FOR XML PATH and its capabilities on TechNet
SQL Server:
just an addition to #marc_s answer - note that xml is case-sensitive, and resulting xml will look like
<Students>
<Student>
<ID>1</ID>
<Name>a</Name>
<Number>123</Number>
<Email>a#a.com</Email>
</Student>
</Students>
and if you'll try to retrieve id, you'll not find anything.
you may want to do something like this:
select
ID as id,
Name as name,
Number as number,
Email as email
from dbo.Table1
for xml path('Student'), root('Students')
=> sql fiddle example.
C#:
you can use WriteXml method:
var ds = new DataSet("Students");
var dt = ds.Tables.Add("Student");
dt.Columns.Add("id", typeof(int));
dt.Columns.Add("name", typeof(string));
dt.Columns.Add("number", typeof(string));
dt.Columns.Add("email", typeof(string));
dt.Rows.Add(1, "a", "123", "a#a.com");
dt.Tables[0].Rows.Add(2, "b", "321", "b#b.com");
dt.Tables[0].Rows.Add(3, "c", "432", "c#c.com");
var stream = new StringWriter();
ds.WriteXml(stream);
or using Linq to XML:
new XElement("Students", dt.AsEnumerable().Select(r =>
new XElement("Student",
new XElement("id", r["id"]),
new XElement("name", r["name"]),
new XElement("number", r["number"]),
new XElement("email", r["email"])
)));
1) Create class called student
[Serializable]
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public string Email { get; set; }
}
2) Get data into List called StudentListfrom Database
3)Then open or create xml file and add values
using (XmlWriter writer = XmlWriter.Create("Student.xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("Students");
foreach (Student student in StudentList)
{
writer.WriteStartElement("Student");
writer.WriteElementString("id", student.ID.ToString());
writer.WriteElementString("name", student.Name.ToString());
writer.WriteElementString("number", student.Number.ToString());
writer.WriteElementString("email", student.Email.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
You can use DataSet.GetXml() function for getting result in XML format file
Though the solution provided by marc is exactly what you need, you may want to have a deeper look at various options in the article Using the FOR XML Clause to Return Query Results as XML.