Linq to XML Add element to specific sub tree - c#

My XML:
<Bank>
<Customer id="0">
<Accounts>
<Account id="0" />
<Account id="1" />
</Accounts>
</Customer>
<Customer id="1">
<Accounts>
<Account id="0" />
</Accounts>
</Customer>
<Customer id="2">
<Accounts>
<Account id="0" />
</Accounts>
</Customer>
</Bank>
I want to add new Account element to lets say Customer with id 2. I know how to add the line what I dont know how do I specify the customer (where do I write the Customer's ID ?)
My LINQ to XML code:
XDocument document = XDocument.Load("database.xml");
document.Element("Bank").Element("Customer").Element("Accounts").Add
(
new XElement
(
"Account", new XAttribute("id", "variable")
)
);
document.Save("database.xml");
Thanks for the help. XML is not my good friend :(

You are almost there, your code will be default add the element to the first Customer. You need to search for the attribute id in collection of Customers whose value is 2 -
document.Element("Bank").Elements("Customer")
.First(c => (int)c.Attribute("id") == 2).Element("Accounts").Add
(
new XElement
(
"Account", new XAttribute("id", "variable")
)
);

I know how to add the line what I dont know how do I specify the customer (where do I write the Customer's ID ?)
You need to find the Customer element with the right ID first. For example:
var customer = document.Root
.Elements("Customer")
.Single(x => (int) x.Attribute("id") == id);
customer.Element("Accounts").Add(new XElement("Account",
new XAttribute("id", "variable")));
Note that this will fail on the Single call if there isn't exactly one Customer element with the right ID. If you want to create a new customer, you'll need to do a bit more work - but presumably that would be in a different call anyway.

var cust = xDoc.Descendants("Customer")
.First(c => c.Attribute("id").Value == "2");
cust.Element("Accounts").Add(new XElement("Account", new XAttribute("id","3") ));

Related

Linq to XML delete parent node

I'm trying to delete a parent from a element in XML.
My XML:
<root>
<Element1 ManagementID="10" />
<Users>
<UserID ManagementID="10">
<Identification IDValue="1" />
<!-- More elements Here -->
</UserID>
</Users>
<!-- More Users elements Here -->
I find my user my its IDValue:
XElement user = (from el in document.Root.Elements("Users").Elements("UserID ").Elements("Identification")
where (string)el.Attribute("IDValue") == myID
select el).FirstOrDefault();
Now, I would like to remove all the user.Parent.Parent
I mean delete the element:
<Users>
<UserID ManagementID="10">
<Identification IDValue="1" />
<!-- More elements Here -->
</UserID>
</Users>
** I'll have many Users elements, that's why first I look for the identification IDValue
I found the solution for who needs it:
I already had the node from my linq so
user.Parent.Parent.Remove()
var user = document.Root
.XPathSelectElements("//UserId")
.FirstOrDefault(userId => userId.Element("Identification").Attribute(XName.Get("IDValue")).Value == myID);
Try this :
List<XElement> users = document.Descendants("Users")
.Where(user => user.Elements("Identification")
.Where(el => (string)el.Attribute("IDValue") != myID)
.Any()).ToList();
XElement element1 = document.Descendants("Element1").FirstOrDefault();
element1.ReplaceNodes(users);

Create XML file from collection list

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

Error on converting XML to object using LINQ

I have this xml:
<?xml version="1.0" encoding="utf-8"?>
<Packet>
<Header>
<Id>1234-1234-1234</Id>
</Header>
<Customers>
<Customer>
<Name>Try</Name>
<Age>20</Age>
</Customer>
</Customers>
</Packet>
And this is how I convert it to object:
XDocument xdoc = XDocument.Load(xml);
List<Customer> customers = (from customer in xdoc.Element("Customers").Element("Customer")
select new Customer
{
Name = customer.Element("Name").Value,
Age = customer.Element("Age").Value
}).ToList();
My problem is when I tried to run this code, I got an exception error saying that object reference not set to an instance.
But when i changed my xml to this:
<?xml version="1.0" encoding="utf-8"?>
<Customers>
<Customer>
<Name>Try</Name>
<Age>20</Age>
</Customer>
</Customers>
It started working and I am getting the name and age. However, the packet and header is one of the requirements on my xml files. How am I gonna do that?
EDIT:
Thanks for all the solutions! They are all working, but may I know what is the best to use (best practices, etc) Thanks!
You can use Descendants() method to find elements in xml tree.
List<Customer> customers = (from customer in xdoc.Descendants("Customer")
select new Customer
{
Name = customer.Element("Name").Value,
Age = customer.Element("Age").Value
}).ToList();
Change your query source to:
xdoc.Root.Element("Customers").Elements("Customer")
Element method looks for the element on current level, which is the root for XDocument. That's why the query didn't work.
Try changing your LINQ query to:
XDocument xdoc = XDocument.Load(xml);
List<Customer> customers = (from customer in xdoc.Element("Packet").Element("Customers").Element("Customer")
select new Customer
{
Name = customer.Element("Name").Value,
Age = customer.Element("Age").Value
}).ToList();
Because your Customers element is located inside your Packet element.

Delete xElement by xAttribute value

I have the following xml document:
<?xml version="1.0" encoding="utf-8"?>
<Categories>
<title>
<Type name="James">
<Field name="ref" value="79" />
<Field name="titleref" value="55" />
</Type>
</title>
</Categories>
I want to delete all of 'title' if the textBox1 text matches
I have the following, I know it doesn't work, but I wanted to see if you can see my logic.
xmldoc.Root.Elements().Where(x => x.Element("Type")).Where (x => x.Attribute("name").Value.Equals(textBox1.Text)).Select(x => x).Single().Remove();
Any help would be great
Thanks
You can use XPath (System.Xml.XPath)
xmldoc.XPathSelectElements(String.Format("//Type[#name='{0}']", textBox1.Text))
.Remove();
xmldoc.Root.Descendants( "Type" )
.Where( x => x.Attribute( "name" ).Value == textBox1.Text )
.Remove();

Inserting elements into a child node, based on a condition

I have the following XML
XElement xe =
new XElement("Schedule",
new XElement("Team",
new XElement("Name", "SomeName"),
new XElement("Dates"),
new XElement("Game",
new XElement("Bench"),
new XElement("Date")
)
)
);
Eventually i'll want to add to the Bench and Date field.
I've tried something like:
foreach(Name name in names)
{
doc.Element("Schedule").Element("Team").Element("Dates")
.Elements()
.FirstOrDefault(item => (string)item.Parent.Element("Name") == name.TeamName)
.Add(
new XElement("Game",
new XElement("Bench", "SomeBench"),
new XElement("Date", SomeDate.ToShortDateString())
)
);
}
I get an Object not set to an instance of an object error.
Any help would be great
EDIT: Forgot to mention that the insertions would happen with a loop
My End result would end up something like:
<Schedule>
<Team>
<Name>SomeName</Name>
<Dates>
<Bench>SomeBench</Bench>
<Date>12/10/2012</Date>
</Dates>
</Team>
<Team>
<Name>SomeName2</Name>
<Dates>
<Bench>SomeBench2</Bench>
<Date>12/13/2012</Date>
</Dates>
</Team>
<Team>
<Name>SomeName3</Name>
<Dates>
<Bench>SomeBench3</Bench>
<Date>12/16/2012</Date>
</Dates>
</Team>
</Schedule>
The original OP's code could have an exception by several reasons:
In doc.Element("Schedule").Element("Team").Element("Dates") the segment Element("Schedule") is extra, if doc == xe, i.e. Schedule is the root xml element.
.Elements() segment is extra as the Dates element in xe does not contain any child elements.
The question is very confusing to me. Nevertheless I'll try to speculate.
foreach (var dates in doc.Elements("Team")
.Where(t => names.Any(n => n.TeamName == t.Element("Name").Value))
.Select(t => t.Element("Dates")))
{
dates.Add(
new XElement("Bench", "SomeBench"),
new XElement("Date", SomeDate.ToShortDateString())
);
}

Categories

Resources