Check against multiple elements with one query? - c#

Is it possible to check multiple elements inside of a root using where? I have a XML sheet setup in such a way that there are multiple elements with the same name (but only sometimes), like so:
<person>
<name>Joe</name>
<food>orange</food>
<food>apple</food>
</person>
<person>
<name>Roger</name>
<food>apple</food>
</person>
I want to be able to check whether or not a person has a specific type of food and then output them into console. Using this method to grab them from the XML sheet:
var query = from c in xml.Root.Descendants("person")
where (string)c.Element("food") == "apple"
select new c.Element("food").Value;
It will only add Roger to the query. I believe this is because apple is second, since when I switch it to being first on the list I get Joe to show up. Is there a way to check to see if the second element meets the where statement as well?

You can try this out.It will return a projection with the name and food just to prove it has picked both:
var query = from c in xml.Root.Elements("person") //Descendants("person")
from f in c.Descendants("food")
where (string)f == "apple"
select new { Food = f.Value,Name = c.Element("name").Value };
Output of query:
{Food = "apple", Name = "joe"}
{Food = "apple", Name = "Roger"}

Your code won't add "Roger", but "apple" to the resulting IEnumerable. I assume you want to select the name element of your person.
As an alternative to the previous answer, you could also search for all apples and then get the name element of their parents.
var result = xml.Root.Descendants("food")
.Where(x=> (string)x == "apple")
.Select(y=> (string)y.Parent.Element("name"));
should do the trick

Using Lambda (short cutting since apple is hard coded):
var doc = XDocument.Parse(#"<root><person><name>Joe</name><food>orange</food><food>apple</food></person><person><name>Roger</name><food>apple</food></person></root>");
var results = doc.Root.Descendants("person")
.Where(p => p.Elements("food").Any(f => f.Value == "apple"))
.Select(p => "apple");
DotNetFiddle Example

Related

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

LINQ to XML - avoid duplicate entries

XML structure:
<Emp>
<Employee username="John"/>
<Employee username="Jason"/>
</Emp>
I don't want to insert the duplicate attributes to the xml file by following linq to xml query
var newEmployee= XElement.Parse(defaultEmployee.ToString());
var q = from p in doc.Descendants("Employee")
let attr = p.Attribute("username")
where attr != null && attr.Value != txtusername.Text
select p;
foreach(var dupes in q)
{
newEmployee.Attribute("username").Value = txtusername.Text ;
doc.root.Add(newEmployee);
doc.save(EmployeeFile);
}
I am trying to just add a new employee with out any duplicates,but my code adds duplicates still.
Can some one look at my query and let me know where am I missing the logic ?
To add a new employee to your xml there is no loop neccessary, nor parsing any default XML, simply:
doc.Root.Add(new XElement("Employee",
new XAttribute("username", txtusername.Text));
It's unclear to me what your loop is for, currently you are selecting any employee that has a different user name, and for each of those you add a new employee node - that doesn't make much sense, I suspect you want to add the new employee only once.
If you wanted to check on the other hand if the employee with the given user name already exists:
bool userExistsAlready = doc.Descendants("Employee")
.Any(x=> (string)x.Attribute("username") == txtusername.Text);
Now you can put a check around the code that adds the new employee:
if(!userExistsAlready)
{
//add new user
}
With this LINQ query you can loop the username attributes, providing a DISTINCT operation:
var q = (from p in newEmployee.Descendants("Employee").Attributes("username")
select (string)p).Distinct();

Linq to XML retrieve single node

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<game>
<name>Space Blaster</name>
<description></description>
<version>1</version>
<fullscreen>false</fullscreen>
<width>640</width>
<height>640</height>
<c2release>6900</c2release>
</game>
It's guaranteed to only have 1 game record in it. I'd like to retrieve each property value, this is what I've tried:
string ArcadeXMLLocation = Settings.GamePergatoryLocation + UserID + "/unzipped/arcade.xml";
XDocument loaded = XDocument.Load(ArcadeXMLLocation);
var q = (from c in loaded.Descendants("game") select (string)c.Element("name")).SingleOrDefault();
Response.Write(q.name);
But I can't seem to take any values like this (intellisense hates it!) Can someone show me how it's done?
I think you just need to get the value of the element:
string val = doc.Descendants("game")
.Select(x => x.Element("name").Value).FirstOrDefault();
As a prerequisite to the above, and so intellisense picks it up, make sure that you import the System.Linq and System.Xml.Linq namespaces.
This will get you all the descendants with the tag "game"
You just need the FirstOrDefault() to get the only record if it exists.
var q = from c in loaded.Descendants("game")
select new { Name = c.Element("name").Value
Description = c.Element("description").Value
};
q.FirstOrDefault();
You query is actually correct (tested and worked for me) for extracting the value of the name node - the (string) cast that you are doing will extract the value of the name node as string and not give you the node object itself, this is one of the shortcuts built into Linq to Xml. All that is left is to print out the name:
Response.Write(q);
var q = (from c in loaded.Descendants("game") select (string)c.Element("name")).SingleOrDefault();
Console.WriteLine(q);
is enough. or to avoid the cast
var q = (from c in loaded.Descendants("game") select c.Element("name").Value).SingleOrDefault();
Console.WriteLine(q);

LINQ Statement WHERE Question

I am returning a list. This is contains the names of xml nodes that cannot be blank in my XML file.
List<Setting> settingList = SettingsGateway.GetBySettingTypeList("VerifyField");
I have a LINQ Statement. I am trying to return all transactions that have empty nodes. The list here is returning the nodes that CANNOT be empty. Does anyone know what I am doing wrong here?
The following code is supposed to Bind the "transactions" to a DataGrid and display the Txn's that have empty nodes which are required.
var transactionList =
from transactions in root.Elements(XName.Get("Transactions")).Elements().AsEnumerable()
where transactions.Elements().Any
(
el =>
//String.IsNullOrEmpty(el.Value) &&
//elementsThatCannotBeEmpty.Contains(el.Name)
settingList.Any(
name => String.IsNullOrEmpty(el.Element(name.SettingValue).Value)
)
)
select new
{
CustomerName = transactions.Element(XName.Get("CustomerName")).Value,
ConfirmationNumber = transactions.Element(XName.Get("ConfirmationNumber")).Value
};
GridView.DataSource = transactionList;
GridView.DataBind();
XML File Example:
<OnlineBanking>
<Transactions>
<Txn>
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>
Okay, first problem: if the element is missing, you'll get a NullReferenceException.
I'd suggest creating a List<string> of the elements which can't be null, to make the query simple. Then:
var requiredElements = settingList.Select(x => x.SettingValue).ToList();
var transactionList = root
.Elements("Transactions")
.Elements("Txn")
.Where(x => requiredElements
.Any(name => string.IsNullOrEmpty((string) x.Element(name)));
I think that should be okay, and slightly simpler than your original code... but to be honest, your original code looks like it should have worked anyway. What did it actually do? You haven't been very clear about the actual results versus the expected ones...
Something like this:
var transactionList =
root
.Elements(XName.Get("Transactions")) //Get <Transaction> elements
.Elements() //Get <Txn> elements
.Where(txn => txn.Elements().Any(e => e.Value == String.Empty)) //Filter <Txn> Elements if it have any element like this: <CustomerStreet2></CustomerStreet2>
.Select(x => new {
PropertyX = x.Element(XName.Get("UserName")),
PropertyY = x.Element(XName.Get("CustomerStreet")),
...
});
Works with:
<OnlineBanking>
<Transactions>
<Txn> <!-- This one matches! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
<Txn> <!-- This one doesn't match! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2>ASDASD</CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>

Why take just one? Linq to XML C#

I can't figure out why my code just taking the first tag and not the rest.
var xml = XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/Themes.xml"));
var q = from f in xml.Descendants("themes")
select new ThemesItem
{
Name = f.Element("theme").Element("name").Value,
Description = f.Element("theme").Element("description").Value,
Author = f.Element("theme").Element("author").Value,
};
return q.ToList();
ThemeItem is just a get set with public string
When i write out this data i use a repeater
Thanks for help :)
That is because the Descendants extension method takes all decendants of the xml node, that is named "themes". Since your themes node is the container for the individual theme tags, there is only one, and when you take .Element on that, you get the first occurence.
This code should work:
var q = from f in xml.Descendants("theme")
select new ThemesItem
{
Name = f.Element("name").Value,
Description = f.Element("description").Value,
Author = f.Element("author").Value,
};
<themes>
<theme>
<name>Standard</name>
<description>standard theme</description>
<author>User 1</author>
<folder>standard</folder>
</theme>
<theme>
<name>Standard</name>
<description>standard theme</description>
<author>User 2</author>
<folder>standard</folder>
</theme>
</themes>
Try using XElement.Load() instead of XDocument.Load()
http://msdn.microsoft.com/en-us/library/bb675196.aspx

Categories