How to write XPath expression to select node name from its value - c#

I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).

Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com

Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}

I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;

Related

How to check the attribute value string from linq in c#

I have a string value in column of database table :-
<Attributes><ProductAttribute ID="322"><ProductAttributeValue><Value>782</Value></ProductAttributeValue></ProductAttribute></Attributes>
There are multiple column with the same format.
Now I need to check ProductAttributeValue and get the data from linQ
currently I am doing by
var id = 782
var string = "<Attributes><ProductAttribute ID="322"><ProductAttributeValue><Value>" + id + "</Value></ProductAttributeValue></ProductAttribute></Attributes>";
var value = sometable.where(x => x.valueString == string).FirstOrDefault();
Is there any way to get direct from linq?
This can be done using LINQ to XML.
using System.Linq;
using System.Xml.Linq;
...
var id = "Value To Find";
var str = "<Attributes><ProductAttribute ID=\"322\"><ProductAttributeValue><Value>" + id + "</Value></ProductAttributeValue></ProductAttribute></Attributes>";
var xml = XDocument.Parse(str);
var val = xml
.Element("Attributes")
.Element("ProductAttribute")
.Element("ProductAttributeValue")
.Element("Value")?.Value;
Since there is only 1 of each element in the xml data structure you can use Element, if there are multiple you can use Elements and operate on them as a collection.
You can filter elements like usual using Where and other extension methods.
var valToFind = "722";
var val = xml
.Element("Attributes")
.Elements("ProductAttribute")
.Where(node => node
.Element("ProductAttributeValue")
?.Element("Value")
?.Value == valToFind
)
.FirstOrDefault();
The above will find the ProductAttribute node that has a ProductAttributeValue Value equal to the valToFind. valToFind is a string for quick comparison against the xml string value.

How to pull ObjectId from an Array in a BsonDocument with C# MongoDriver 2.5

I'm trying to port over JavaScript code that interacts with a MongoDb to C# .NET and having problems with a delete/pull operation. I can't seem to figure out how to iterate over the Ponies array and remove a single ObjectId by matching to a passed in ObjectId.
{
"_id" : ObjectId("388e8a66fe2af4e35c60e"),
"Location" : "rainbowland",
"Ponies" : [
ObjectId("388e8a66fe2af4e35c24e83c"),
ObjectId("388e8a66fe2af4e35c24e860"),
ObjectId("388e8a66fe2af4e35c24e83d")
]
}
I've tried several different ways based on what I've found online but nothing seems to work. When I check the collection to see if 388e8a66fe2af4e35c24e860 was removed from the Ponies array I see that it wasn't removed. Everytime I execute the code and look at the ModifiedCount for resultPonies it shows zero. Two examples of what I've tried doing are below.
var pony = new Pony() { Id = new ObjectId("388e8a66fe2af4e35c24e860") }
var pullPonies = Builders<Ranch>.Update.PullFilter(x => x.Ponies,
y => y.Id == pony.Id);
var filterPonies = Builders<Ranch>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
OR
var pullPonies = Builders<BsonDocument>.Update.PullFilter("Ponies",
Builders<ObjectId>.Filter.Eq("ObjectId", pony.Id));
var filterPonies = Builders<BsonDocument>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
Here is the JavaScript code that I believe works but haven't been able to test yet.
db.collection("Ranches").update({}, {$pull: {Ponies: pony._id}}, {multi: true});
Ok, I found a solution for the problem. Since I'm not actually going to be filtering within the array of objects, they are all ObjectIds in a BsonArray, I need to find where the value in the array matches the given ObjectId. In those cases the array item should be pulled out. If the object in the array had other properties then I would probably want to use the PullFilter. But in this case I don't need to filter the additional level. That is at least what I believe I was doing wrong. Here are the solutions
var ponyId = new ObjectId("388e8a66fe2af4e35c24e860");
var pullPonies = Builders<Ranch>.Update.Pull(x => x.Ponies, ponyId);
// This line is to retrieve all Ranch objects in the collection
var filterPonies = Builders<Ranch>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
Or
var ponyId = new ObjectId("388e8a66fe2af4e35c24e860");
var pullPonies = Builders<BsonDocument>.Update.Pull("Ponies", ponyId));
// This line is to retrieve all Ranch objects in the collection
var filterPonies = Builders<BsonDocument>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);

DocumentDB filter an array by an array

I have a document that looks essentially like this:
{
"Name": "John Smith",
"Value": "SomethingIneed",
"Tags: ["Tag1" ,"Tag2", "Tag3"]
}
My goal is to write a query where I find all documents in my database whose Tag property contains all of the tags in a filter.
For example, in the case above, my query might be ["Tag1", "Tag3"]. I want all documents whose tags collection contains Tag1 AND Tag3.
I have done the following:
tried an All Contains type linq query
var tags = new List<string>() {"Test", "TestAccount"};
var req =
Client.CreateDocumentQuery<Contact>(UriFactory.CreateDocumentCollectionUri("db", "collection"))
.Where(x => x.Tags.All(y => tags.Contains(y)))
.ToList();
Created a user defined function (I couldn't get this to work at all)
var tagString = "'Test', 'TestAccount'";
var req =
Client.CreateDocumentQuery<Contact>(UriFactory.CreateDocumentCollectionUri("db", "collection"),
$"Select c.Name, c.Email, c.id from c WHERE udf.containsAll([${tagString}] , c.Tags)").ToList();
with containsAll defined as:
function arrayContainsAnotherArray(needle, haystack){
for(var i = 0; i < needle.length; i++){
if(haystack.indexOf(needle[i]) === -1)
return false;
}
return true;
}
Use System.Linq.Dynamic to create a predicate from a string
var query = new StringBuilder("ItemType = \"MyType\"");
if (search.CollectionValues.Any())
{
foreach (var searchCollectionValue in search.CollectionValues)
{
query.Append($" and Collection.Contains(\"{searchCollectionValue}\")");
}
}
3 actually worked for me, but the query was very expensive (more than 2000 RUs on a collection of 10K documents) and I am getting throttled like crazy. My result set for the first iteration of my application must be able to support 10K results in the result set. How can I best query for a large number of results with an array of filters?
Thanks.
The UDF could be made to work but it would be a full table scan and so not recommended unless combined with other highly-selective criteria.
I believe the most performant (index-using) approach would be to split it into a series of AND statements. You could do this programmatically building up your query string (being careful to fully escape and user-provided data for security reasons). So, the resulting query would look like:
SELECT *
FROM c
WHERE
ARRAY_CONTAINS(c.Tags, "Tag1") AND
ARRAY_CONTAINS(c.Tags, "Tag3")

How to parse nested elements using LINQ to XML

I have an XML file with multiple checkItem elements. I need to save each checkItem element into a database. I'm having a difficult time getting exactly what I need using the query below.
<checkItem>
<checkItemType>check</checkItemType>
<checkAmount>195000</checkAmount>
<nonMICRCheckData>
<legalAmount>195000</legalAmount>
<issueDate>2010-04-30</issueDate>
<other>PAY VAL 20 CHARACTER</other>
</nonMICRCheckData>
<postingInfo>
<date>2013-05-01</date>
<RT>10108929</RT>
<accountNumber>111111111</accountNumber>
<seqNum>11111111</seqNum>
<trancode>111111</trancode>
<amount>195000</amount>
<serialNumber>1111111</serialNumber>
</postingInfo>
<totalImageViewsDelivered>2</totalImageViewsDelivered>
<imageView>
<imageIndicator>Actual Item Image Present</imageIndicator>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Front</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Front.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Rear</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Rear.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
</imageView>
</checkItem>
Here is the query I've been working with. I've tried several different ways with no luck. Without the use of .Concat, I cannot get the other elements; however, using .Concat does not allow me to get all values in a manageable format. I need to separate the Front and Rear imageViews based on the ViewSide value, and only need the imageRefKey and imageFileLocator values from the imageView element. Can anyone point me in the right direction?
var query = doc.Descendants("checkItem")
//.Concat(doc.Descendants("postingInfo"))
//.Concat(doc.Descendants("imageViewLocator"))//.Where(x => (string)x.Element("ViewSide") == "Front"))
//.Concat(doc.Descendants("imageViewInfo").Where(x => (string)x.Element("ViewSide") == "Rear"))
.Select(x => new {
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
//FrontViewSide = (string) x.Element("ViewSide"),
//RearViewSide = (string) x.Element("BViewSide")
});
You can easily get nested elements of any XElement by just calling the Elements() method of that instance, then calling Select() on that collection, to created a nested collection of an anonymous type in your main anonymous type.
var query = doc.Elements("checkItem")
.Select( x =>
new
{
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
ImageViews = x.Element("ImageView").Elements("ImageViewInfo")
.Select(iv=>
new
{
Format = iv.Element("Format").Element("Baseline").Value
// more parsing here
}
}

c# xml parsing - conversion from php

I currently have my application/project setup on PHP and I am trying to get it working in c# so I can build an application around it.
I have come across some parts of the code which I am looking help with.
XML data from: http://api.eve-central.com/api/marketstat?usesystem=30000142&hours=48&typeid=34&typeid=456
Above is XML data from a certain system containing 2 typeids (same as the other XML), again this will be around 100 items at a time.
I am using this code at the moment:
XDocument doc = XDocument.Load("http://api.eve-central.com/api/marketstat?typeid=34&typeid=35&usesystem=30000142");
var id = from stats in doc.Root.Elements("marketstat")
from type in stats.Elements("type")
select new
{
typeID = type.Attribute("id").Value
};
foreach (var itemids in id)
{
kryptonListBox4.Items.Add(itemids.typeID);
};
Which populates the ListBox as 34 and 456.
What I need is to be able to add the other xml data such as min sell and max buy
I can get the first min sell like this:
string minSell = doc.Descendants("sell")
.First()
.Element("min")
.Value;
But I need to have the minsell in relation to the typeID and being able to work with the data.
Second Problem
XML data from http://api.eve-marketdata.com/api/item_history2.xml?char_name=demo&days=10&region_ids=10000002&type_ids=34,456
Above is XML data from a certain region and contains 2 type_ids (this will be a much larger list when completed around 100 items at a time).
I have tried to use similar code as above but I cannot get it to return the correct data.
I need to be able to get the volume in total for each typeid
In PHP I use this:
foreach ($xml -> result -> rowset-> row as $row)
{
$id = (string)$row['typeID'];
$volume = $row['volume'];
if (!isset($volumes[$id]))
{
$volumes[$id] = 0;
}
$volumes[$id] = $volumes[$id] + $volume;
}
Any help would be greatly appreciated!
//Edit: Looks like I can use
var vRow = from xmlRow in marketstats.Descendants("emd").Descendants("result").Descendants("rowset").Descendants("row")
select xmlRow;
for the 2nd problem but I cannot seem to get the multidimensional array to work
For your second problem if my understanding is right this will be close to ur need.
var strxml = File.ReadAllText(#"D:\item_history2.xml");
var xml = XElement.Parse(strxml);
var typeIDs = (from obj in xml.Descendants("row")
select obj).Select(o => o.Attribute("typeID").Value).Distinct();
Dictionary<string, long> kv = new Dictionary<string, long>();
foreach (var item in typeIDs)
{
var sum = (from obj in xml.Descendants("row")
select obj).Where(o => o.Attribute("typeID").Value == item).Sum(p => long.Parse(p.Attribute("volume").Value));
kv.Add(item, sum);
}
In the dictionary you will have the sum of volumes against each typeID in such a way
typeID as key and sum of volume as value in Dictionary kv.
For your first problem,
Checkout this,
var minsell = (from obj in xml.Descendants("type")
select new
{
typeid = obj.Attribute("id").Value,
minsell = obj.Descendants("sell").Descendants("min").FirstOrDefault().Value
}
).ToArray();
This will give you minsell value in relation with typeid.
I guess this what you expects ?
If wrong please comment it.

Categories