Linq never finding element in XDocument - c#

I have the following XDocument called XDoc:
<?xml version="1.0" encoding="utf-8"?>
<DatabaseList>
<Database DatabaseName="c2501_data">
<Plugin PluginName="FooPlugin" LastRun="1/21/2013 3:22:08 PM" />
<Plugin PluginName="SpecialPlugin" LastRun="2013-01-21T15:22:09.3791103-05:00" />
<Plugin PluginName="BarPlugin" LastRun="2013-01-21T15:23:13.0964814-05:00" />
</Database>
</DatabaseList>
I'm writing a program that searches to see when the last time a plugin was run on a database, if at all. I use the following two pieces of code to figure out if an entry exists for a plugin on a database:
var h = (from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value=="FooPlugin"
&& el.Attribute("DatabaseName").Value=="c2501_data"
select el.Element("Plugin"));
var e = (from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value=="BarPlugin"
&& el.Attribute("DatabaseName").Value == "c2501_data"
select el.Element("Plugin"));
if ((from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value == "BarPlugin"
&& el.Attribute("DatabaseName").Value == "c2501_data"
select el.Element("Plugin")).Count() == 0)
{
XElement SpecialPlugin = new XElement("Plugin",
new XAttribute("PluginName", "BarPlugin"),
new XAttribute("LastRun", DateTime.Now));
var CurNode = from node in XDoc.Root.Elements("Database")
where (string)node.Attribute("DatabaseName").Value == "c2501_data"
select node;
foreach (var node in CurNode)
node.Add(SpecialPlugin);
XDoc.Save(RuntimesPath);
//XDoc.Root.Elements("Database").Attribute("DatabaseName").
}
The problem that I'm having is that even though there is clearly an entry for BarPlugin, the count will always return 0 and e will always be unable to create an enumberable. Can anyone explain to me why this might be? FooPlugin always works correctly and returns the Plugin information for h.
Thanks for any help.

You're selecting a Database element where it contains a child element called Plugin with a given name. Since you have only one Database element, you're getting the same outer element each time. You then take that database element and return the first Plugin child, which will always be Foo, in this case. You need to find the appropriate Database element and then query through each of the child elements so you can return them:
public static XElement GetPlugin(XDocument XDoc, string databaseName, string pluginName)
{
var h = from database in XDoc.Root.Elements("Database")
where database.Attribute("DatabaseName").Value == databaseName
from plugin in database.Elements("Plugin")
where plugin.Attribute("PluginName").Value == pluginName
select plugin;
return h.FirstOrDefault();
}
Or, if you prefer, in method syntax:
var q = XDoc.Root.Elements("Database")
.Where(db => db.Attribute("DatabaseName").Value == databaseName)
.SelectMany(db => db.Elements("Plugin"))
.Where(plugin => plugin.Attribute("PluginName").Value == pluginName);
return q.FirstOrDefault();

Try this:
var db = XDoc.Root.Elements("Database");
var z = (from el in db.Elements("Plugin")
where el.Attribute("PluginName").Value == "BarPlugin"
&& el.Parent.Attribute("DatabaseName").Value == "c2501_data"
select el).FirstOrDefault();
if(z != null)
.....
I'm using Elements() method, to get all child elements and Parent property to look for the parent element "DatabaseName".
Problem in your code is that your el.Element() is searching only for the first element, thus it can find only "FooPlugin", which is on the first position in the xml.
From MSDN doc Element():
Gets the first (in document order) child element with the specified XName.

Related

"context instance" for a XElement linq to XML query using a where clause

I want to return a list of string values from an XElement collection and I get this error when constructing my code and don't see what I'm missing.
Here's a section of the class I've written concering the problem:
private XElement _viewConfig;
public ViewConfiguration(XElement vconfig)
{
_viewConfig = vconfig;
}
public List<string> visibleSensors()
{
IEnumerable<string> sensors = (from el in _viewConfig
where el.Attribute("type").Value == "valueModule"
&& el.Element.Attribute("visible") = true
select el.Element.Attribute("name").Value);
return sensors.ToList<string>();
}
The XElement collection is of the form
<module name="temperature" type="valueModule" visible="true"></module>
<module name="lightIntensity" type="valueModule" visible="true"></module>
<module name="batteryCharge" type="valueModule" visible="true"></module>
<module name="VsolarCells" type="valueModule" visible="false"></module>
First of all XElement is not an IEnumerable therefore the first line from el in _viewConfig is not valid. If this is coming from a valid XML file, I presume the <module> elements are contained inside a parent element (e.g., <modules>). If you make _viewConfig to point to modules then the following will work:
IEnumerable<string> sensors = (
from el in _viewConfig.Elements()
where el.Attribute("type").Value == "valueModule"
&& el.Attribute("visible").Value == "true"
select el.Attribute("name").Value);
Also note that type of el is XElement, therefore it doesn't have a property called Element which I also removed from above (along with a few syntax errors that I had to fix: usage of == instead of = for comparison, and using string literal "true" instead of boolean true to compare the value of an attribute textual value).

Cant delete XmlElement using Linq query

I have a very basic linq query to be able to delete one node from xml file.But when ı run this code I am getting this exception below.
Sequence contains no elements
then I have used FirstOrDefault() instead First() ( as mentioned earlier posts) and this time Exection message turned into this
Object reference not set to an instance of an object.
This my code
protected void Page_Load(object sender, EventArgs e)
{
XDocument doc = XDocument.Load(Server.MapPath("Kitaplar.xml"));
var toDelete = (from data in doc.Elements("Kitap") where data.Attribute("id").Value == "1" select data).FirstOrDefault();
toDelete.Remove();
doc.Save(Server.MapPath("Kitaplar.xml"));
}
And this is the xmlfile
<?xml version="1.0" encoding="utf-8"?>
<Kitaplar>
<Kitap id="1">
<Kitapadi>asasa</Kitapadi>
<Yazar>sasas</Yazar>
<Sayfa>22</Sayfa>
</Kitap>
<Kitap id="2">
<Kitapadi>jhjh</Kitapadi>
<Yazar>kjkj</Yazar>
<Sayfa>33</Sayfa>
</Kitap>
<Kitap id="3">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
<Kitap id="4">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
</Kitaplar>
Everyting looks ok to me.what am i doing wrong ?
I took the liberty of writing a method that takes the file, element and ID; then removes the Element accordingly.
private bool DeleteRowWithID(string fileName, string element, string id)
{
XDocument doc = XDocument.Load(fileName);
if (doc.Root == null)
return false;
XElement toRemove = doc.Root.Elements(element).Where(e => e.Attribute("id").Value == id).FirstOrDefault();
if (toRemove == null)
return false;
toRemove.Remove();
doc.Save(fileName);
return true;
}
Above method loads the XmlDocument in an XDocument (which allows LINQ to XML). It checks if the root isn't empty, then finds the element you specified.
It checks if the element exists; then removes that element from the document, and save the made removal.
Last, it returns true to indicate that the element has actually been removed.
If you just want the element and stick to your method, use the following:
XElement toRemove = doc.Root.Elements("Kitap").Where(e => e.Attribute("id").Value == "1").FirstOrDefault();

How properly work with LINQ to XML?

I have generated such xml file
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>212</numRequest>
<IDWork>12</IDWork>
<NumObject>21</NumObject>
<lvlPriority>2</lvlPriority>
<NumIn1Period>21</NumIn1Period>
</Single_Request>
</Requestes>
My aim is to get IDWork,numRequest and etc elements. I tried to get them this way:
foreach (XElement el in doc.Root.Elements())
{
if (el.Name == "Single_Request")
{
string num = el.Elements("numRequest").Value;
// but he says, that he cant do this .Value because it doest exist at all
}
}
How to fix this?
You have this error, because Elements("numRequest") returns collection of elements, not single element. You should use Element("numRequest") instead.
Also I suggest you to use query for getting elements by name instead of enumerating all elements and verifying their names:
var request = doc.Root.Element("Single_Request");
var num = (int)request.Element("numRequest");
Usually you use anonymous types or custom objects to group values parsed from xml:
var query = from r in doc.Root.Elements("Single_Request")
where (int)r.Attribute("num") == 1 // condition
select new {
NumRequest = (int)request.Element("numRequest"),
IdWork = (int)request.Element("IDWork"),
NumObject = (int)request.Element("NumObject")
};
var request = query.SinlgleOrDefault();
// use request.IdWork

Find element with specific attribute in xml?

I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)

Linq to XML - Find an element

I am sure that this is basic and probably was asked before, but I am only starting using Linq to XML.
I have a simple XML that i need to read and write to.
<Documents>
...
<Document>
<GUID>09a1f55f-c248-44cd-9460-c0aab7c017c9-0</GUID>
<ArchiveTime>2012-05-15T14:27:58.5270023+02:00</ArchiveTime>
<ArchiveTimeUtc>2012-05-15T12:27:58.5270023Z</ArchiveTimeUtc>
<IndexDatas>
<IndexData>
<Name>Name1</Name>
<Value>Some value</Value>
<DataType>1</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
<IndexData>
<Name>Name2</Name>
<Value>Some value</Value>
<DataType>3</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
...
</IndexDatas>
</Document>
...
</Documents>
I have a "Documents" node that contains bunch of "Document" nodes.
I have GUID of the document and a "IndexData" name.
I need to find the document by GUID and check if it has "IndexData" with some name.
If it does not have it i need to add it.
Any help would be apreciated, as i have problem with reading and searching trough elements.
Currently I am trying to use (in C#):
IEnumerable<XElement> xmlDocuments = from c in XElement
.Load(filePath)
.Elements("Documents")
select c;
// fetch document
XElement documentElementToEdit = (from c in xmlDocuments where
(string)c.Element("GUID").Value == GUID select c).Single();
EDIT
xmlDocuments.Element("Documents").Elements("Document")
This returns no result, even tho xmlDocuments.Element("Documents") does. It looks like i cant get Document nodes from Documents node.
You can find those docs (docs without related name in index data) with below code, after that you could add your elements to the end of IndexData elements.
var relatedDocs = doc.Elements("Document")
.Where(x=>x.Element("GUID").Value == givenValue)
.Where(x=>!x.Element("IndexDatas")
.Elements("IndexData")
.Any(x=>x.Element("Name") == someValue);
This should work:
var x = XDocument.Load(filePath);
// guid in your sample xml is not a valid guid, so I changed it to a random valid one
var requiredGuid = new Guid("E61D174C-9048-438D-A532-17311F57ED9B");
var requiredName = "Name1";
var doc = x.Root
.Elements("Document")
.Where(d => (Guid)d.Element("GUID") == requiredGuid)
.FirstOrDefault();
if(doc != null)
{
var data = doc.Element("IndexDatas")
.Elements("IndexData")
.Where(d => (string)d.Element("Name") == requiredName)
.FirstOrDefault();
if(data != null)
{
// index data found
}
else
{
// index data not found
}
}
else
{
// document not found
}

Categories

Resources