I need to insert only unique values in my XML database based on the "userName" value. For example there can't be two players which has same name in the database. The implementation of my xml database which is named as "MyXML.xml" is like that :
<?xml version="1.0" encoding="utf-8"?>
<Players>
<player name="cag" score="5555" />
<player name="cihan" score="1222" />
<player name="can" score="333" />
</Players>
and my related code in order to detetect duplication is:
public bool insertUserDetails(string userName,float userScore){
XDocument doc = Document.Load(HttpContext.Current.Server.MapPath("MyXML.xml"));
var duplicate = doc.Element("Players").Elements("player").Where(x =>(string)x.Value == userName).SingleOrDefault();
if (duplicate != null){
return false;
}
else{
return true;
}
}
when I try to insert a duplicate value for example "can" and "333", var duplicate value turns out to be null. How can I fix this problem ?
"can" is defined as a attribute in your XML; It is not a value. Value is something which comes in between open and closing tags of xml.
For example, if you have a xml element like this
<SomeTag name="somename">Hello world</SomeTag>
Then, SomeTag is the element name, somename is the attribute value of attribute "name" and "Hello world" is the value of the xml element itself.
So, You need to find the attribute using Attribute method to access its value.
var duplicate = doc.Element("Players")
.Elements("player")
.Where(x=>x.Attribute("name").Value == userName)
.SingleOrDefault();
You're iterating over the Value of each player element, rather than the Attributes of those elements. Since there's no text inside the player element, the value is always an empty string, and thus never matched the name you're looking for. Instead, select the .GetAttribute("name") for each element
var duplicate = doc
.Element("Players")
.Elements("player")
.Select(ele => ele.GetAttribute("name"))
.Where(att =>(string)att.Value == userName)
.SingleOrDefault();
you're comparing the value of an XElement, "x", to userName.
you should compare the "name" attribute of x to it.
var duplicate = doc.Element("Players").Elements("player").Where(x => (string)x.Attribute("name").Value == userName).SingleOrDefault();
Related
I'm just creating an app that stores the settings in a xml file.
If I now want to save a new parameter, I need to check if that parameter already exists and update it or add it to the document.
Actually I solve it this way:
XDocument xDocument = XDocument.Load(appDataFolder + #"\settings.xml");
foreach(XElement xElement in xDocument.Descendants("Settings"))
{
if(xElement.Element("projectFile") != null)
xElement.Element("projectFile").Value = projectFile;
else
xElement.Add(new XElement("projectFolder", projectFile));
if (xElement.Element("projectFolder") != null)
xElement.Element("projectFolder").Value = projectFolder;
else
xElement.Add(new XElement("projectFolder", Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments)));
}
xDocument.Save(appDataFolder + #"\settings.xml");
This is the actual settings.xml:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--System-Settings-->
<Settings version="1.0.0.0">
<projectFile>C:\Users\Public\Documents\project.prj</projectFile>
<projectFolder>C:\Users\Public\Documents</projectFolder>
</Settings>
Isn't there a more handy way that automatically adds an element, if it doesn't exist?
System.Xml.Linq.XElement actually has a method that does exactly that, namely SetElementValue.
It takes an XName and the desired content, adding or updating according to your intuition.
Both
var xml = XElement.Parse("<outer></outer>");
xml.SetElementValue("inner", 2);
and
var xml = XElement.Parse("<outer><inner>1</inner></outer>");
xml.SetElementValue("inner", 2);
result in
<outer><inner>2</inner></outer>
As we can see from the remarks section, it was designed with your use case in mind.
This method is designed to make it easy to maintain a list of
name/value pairs as a set of children elements. When maintaining the
list, you need to add pairs, modify pairs, or delete pairs. If you
call this method passing a name that does not exist as a child
element, this method creates a child element for you. If you call this
method passing the name of an existing child element, this method
modifies the value of the child element to the value that you specify.
If you pass null for value, this method removes the child element.
I am trying to restrict duplicate entry to an XML file and below is the XML file.
<?xml version="1.0" standalone="yes"?>
<Info>
<Details>
<ID>Ryan</ID>
</Details>
<Details>
<ID>Danny</ID>
</Details>
</Info>
Now if I try to add Ryan or Danny again to the ID I should alert like user name already exists.
I'm using the below code and it doesn't work. strName is a string and has username value to be added. Can anyone provide suggestions?
XDocument xDoc = XDocument.Load(Server.MapPath("~/Info.xml"));
bool userExistsAlready = xDoc.Descendants("Details").Any(x => (string)x.Attribute("ID") == strName);
if (userExistsAlready)
{
//alert
}
Try this way:
bool userExistsAlready = xDoc.Descendants("Details")
.Elements("ID")
.Any(x => x.Value == "Ryan");
The problem with your code is that it tries to access attribute ID. But ID is in fact another XML element contained inside element <Details>.
You could set ID as an attribute of Details and then check if that entry exist using the XmlDocument method GetElementByID, or implement a for cycle that checks out the property InnerText of every element in the array resulting from the call to GetElementsByName method.
I have this XML:
<Config>
<EmpFieldsMap>
<Employee>
<Field>
<Name update = "false">EmpNumber</Name>
</Field>
<Field>
<Name insert = "true">EmpName</Name>
</Field>
<Field>
<Name insert = "true">EmpDesignation</Name>
</Field>
</Employee>
</EmpFieldsMap>
</Config>
My application will do an an INSERT or UPDATE for which the fields will come from this xml.
Each tag will have either the insert or the update attribute as shown in the snippet above.
For Insert all the tags that have the attribute
insert = "true"
and the tags that don't have this attribute, in this case the 'EmpNumber', have to be considered.
The same applies for update.
This code gives me all the tags with the insert attribute set to true:
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where p.Element("Name").Attribute("insert") != null
&& p.Element("Name").Attribute("insert").Value == "true"
select p.Element("Name").Value;
Removing the check for null
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where p.Element("Name").Attribute("insert").Value == "true"
select p.Element("Name").Value;
gives
Object Reference not set to an instance
error.
I am having trouble composing a query that will also include the tags where the attribute is not present.
Can someone please help me with this?
Regards.
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where (p.Element("Name").Attribute("insert") ?? "true") == "true"
select p.Element("Name").Value;
With XPath and Linq it even simpler:
XPathSelectElements(#"Config/EmpFieldsMap/Employee/Field/Name[#insert='true']")
Also for this particular xml you can use global search for name elements:
var insertTags = xdoc.XPathSelectElements(#"//Name[#insert='true']")
.Select(n => (string)n);
Or with Linq query syntax:
var insertTags = from n in xdoc.Descendants("Name")
where (string)n.Attribute("insert") == "true"
select (string)n;
When you cast node value to string, it will not throw exception if node is missing. Simply null will be returned. So, you don't need all that stuff (which is btw even not compilable):
(p.Element("Name").Attribute("insert") ?? "true") == "true"
One more edit. If you are dealing with booleans, then use booleans instead of strings:
var insertTags = from n in xdoc.Descendants("Name")
where (bool?)n.Attribute("insert") == true
select (string)n;
How it works? Nullable boolean will have null value for missing attributes. Comparing bool? which do not have value with any boolean value produces false. So, you will get only those elements, which have required attribute, and which have true for that attribute.
If I have a simple XML document such as
<case id="37d3c93c-3201-4002-b24f-08e1221c3cb7">
<party id="26dad8c5-9487-48f2-8911-1d78c00095b2">...</party>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<item relatedCaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">...</illustration>
</case>
I have code that changes the id attribute of the case element. I am now looking for some LINQ code that would help me search all elements that have an attribute value that matches the old value so I can replace it with the new value. I do not have a list of attribute names, and would need to search the entire document.
Any tips/ideas?
Thanks!
Something like this:
var matches = doc.Descendants()
.Where(x => x.Attributes()
.Any(attr => attr.Value == oldValue));
Or if you're just trying to replace the values, you only need the attributes themselves:
var attributes = doc.Descendants()
.Attributes()
.Where(attr => attr.Value == oldValue)
.ToList();
foreach (var attribute in attributes)
{
attribute.Value = newValue;
}
It's entirely possible that the copy to a list isn't necessary in this case, but I generally prefer to make a copy when mutating an XDocument to avoid confusing things. (It's certainly necessary when you're removing an element or something like that - just setting the value of an attribute probably doesn't affect things.)
I was working on a bunch of XMLs that all share an attribute that contains the string "name" in them. The following code selects the attribute with string "name" in it and assign a new value to it.
public void updateXmlFile(string strFileName)
{
try
{
//Load the Document
XmlDocument doc = new XmlDocument();
doc.Load(strFileName);
//Set the changed Value
string newValue = GetUniqueKey();
//Select all nodes in the XML then choose from them
//the first node that contain string "name" in it
XmlNodeList list = doc.SelectNodes("//#*");
XmlNode filteredNode = list.Cast<XmlNode>()
.First(item => item.Name.ToLower().Contains("name"));
//Assign the newValue to the value of the node
filteredNode.Value = newValue;
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
Now a new XMLs were added that dosen't have the string "name" in them, so instead of modifying the attribute with string "name" in it I decided to simply modify the last attribute no matter what it was (not the first)
Can anybody tell me how to do that?
EDIT
Here is an example of my XML
<?xml version="1.0" encoding="utf-8"?>
<CO_CallSignLists Version="24" ModDttm="2010-09-13T06:45:38.873" ModUser="EUADEV\SARE100" ModuleOwner="EUADEVS06\SS2008" CreateDttm="2009-11-05T10:19:31.583" CreateUser="EUADEV\A003893">
<CoCallSignLists DataclassId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" EntityId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" MissionID="4CF71AB2-0D92-DE11-B5D1-000C46F3773D" BroadcastType="S" DeputyInSpecialList="1" SunSpots="1537634cb70c6d80">
<CoCallSigns EntityId="DEBF1DDB-3C92-DE11-A280-000C46F377C4" CmdID="C45F3EF1-1292-DE11-B5D1-000C46F3773D" ModuleID="6CB497F3-AD63-43F1-ACAE-2C5C3B1D7F61" ListType="HS" Name="Reda Sabassi" Broadcast="INTO" PhysicalAddress="37" IsGS="1" HCId="0" CommonGeoPos="1" GeoLat="0.0000000" GeoLong="0.0000000">
<CoRadios EntityId="E1BF1DDB-3C92-DE11-A280-000C46F377C4" RadioType="HF" />
</CoCallSigns>
</CoCallSignLists>
</CO_CallSignLists>
#Alex: You notice that the "SunSpots" attribute (last attribute in the first child element) is successfully changed. But now when I wanna load the XML back into the DB it gives me an error
Here is the modified code
public void updateXmlFile(string strFileName)
{
try
{
XDocument doc = XDocument.Load(strFileName);
XAttribute l_attr_1 = (doc.Elements().First().Elements().First().Attributes().Last());
l_attr_1.Value = GetUniqueKey();
Console.WriteLine("Name: {0} Value:{1}", l_attr_1.Name, l_attr_1.Value);
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
I was thinking of making an if statment which checks if the XML has an attribute that contains string "name" in it (since most of my XMLs has an attribute that contains name in them) if it does then change the attribute's value if not look for the last attribute and change it.. not the best solution but just throwing it out there
Then definitely use Linq to XML.
Example:
using System.Xml.Linq;
string xml = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Commands Version=""439"" CreateUser=""Reda"">
<CmCommands DataclassId=""57067ca8-ef96-4d2e-a085-6bd7e8b24126"" OrderName = ""Tea"" Remark=""Black"">
<CmExecutions EntityId=""A9A5B0F2-6AB4-4619-9106-B0F85F86EE01"" Lock=""n"" />
</CmCommands>
</Commands>";
XDocument x = XDocument.Parse(xml);
Debug.Print(x.Elements().First().Elements().First().Attributes().Last().Value);
// Commands ^ CmCommands ^ Remark ^
That is, word for word, the last attribute of the first child of the first element.
You can also query for element/attribute names, like:
Debug.Print(x.Descendants(XName.Get("CmCommands", "")).First().Attribute(XName.Get("Remark", "")).Value);
And of course you can use all of the Linq goodness like Where, Select, Any, All etc.
Note: replace XDocument.Parse with XDocument.Load if appropriate etc.
I've not tested this but you should be able to do all of this in the XPath expression. Something like this:
//#*[contains(node-name(.), 'name')][last()]
This will return only the last attribute with the string name anywhere in its name.
If you only want the last attribute, irrespective of it's name, use this:
//#*[last()]
Look at class XmlAttributeCollection. You can get this collection by reading property Attributes of XmlNode. Just get the last by index.
Instead of .First(), use an extension method like this:
public static T LastOrDefault<T>(this IEnumerable<T> list)
{
T val = null;
foreach(T item in list)
{
val = item;
}
return val;
}