I have an xml document like this:
<?xml version="1.0" encoding="utf-8" ?>
<demographics>
<country id="1" value="USA">
<state id ="1" value="California">
<city>Long Beach</city>
<city>Los Angeles</city>
<city>San Diego</city>
</state>
<state id ="2" value="Arizona">
<city>Tucson</city>
<city>Phoenix</city>
<city>Tempe</city>
</state>
</country>
<country id="2" value="Mexico">
<state id ="1" value="Baja California">
<city>Tijuana</city>
<city>Rosarito</city>
</state>
</country>
</demographics>
How do I setup LINQ queries for doing things like:
1. Get All Countries
2. Get All States in a Country
3. Get All Cities inside a state of a paricular country ?
I gave it a try and I am kind of confused when to use Elements["NodeName"] and Descendants etc. I know I am not the brightest XML guy around. Is the format of the XML file even correct for simple traversal?
To load the document from a file:
XDocument document = XDocument.Load("input.xml");
To get all the countries' names:
IEnumerable<string> countries = document
.Descendants("country")
.Select(element => element.Attribute("value").Value);
To get all the states that are inside the country "USA":
IEnumerable<string> states = document
.Descendants("country")
.Where(element => element.Attribute("value").Value == "USA")
.Elements("state")
.Select(element => element.Attribute("value").Value);
To get all the cities inside USA/California:
IEnumerable<string> cities = document
.Descendants("country")
.Where(element => element.Attribute("value").Value == "USA")
.Elements("state")
.Where(element => element.Attribute("value").Value == "California")
.Elements("city")
.Select(element => element.Value);
You might also want to look at XPath queries (you need using System.XML.XPath):
IEnumerable<string> cities = document
.XPathSelectElements("/demographics/country[#value='USA']/state[#value='California']/city")
.Select(element => element.Value);
Like this:
var countries = document.Root.Elements("country");
var states = country.Elements("state");
var cities = state.Elements("city");
var doc = XDocument.Load("myxml.xml");
var countries = doc.Descendants("country")
.Attributes("value")
.Select(a => a.Value);
var states = doc.Descendants("country")
.Single(country => country.Attribute("value").Value == "USA")
.Elements("state")
.Attributes("value")
.Select(a => a.Value);
var cities = doc.Descendants("state")
.Single(state => state.Attribute("value").Value == "California")
.Elements("city")
.Select(e => e.Value);
The result will have countries, states, and cities as type IEnumerable<string>.
Also worth noting that execution (i.e. parsing) will be delayed until you actually enumerate the values in those IEnumerable<string> variables. This can sometimes cause unintended performance issues. For example, if you're planning to display all the data anyway, and you databind it to some UI control, the user interface can get sluggish as it realizes that it does need to parse this after all. (It might even block the UI thread, instead of your worker thread? Not sure.) To fix this, add .ToList() to the end, to get non-deferred List<string>s instead.
Related
I'm trying to use LinQ Intersect (or equivalent) into an IQueryable method but it seems like I'm doing it wrong.
I have some PRODUCTS that match some SPECIFITY (like colors, materials, height...), those specifications have different values, for example:
color : blue, red, yellow
height : 128cm, 152cm...
I need to get the products that match ALL the list of couple specifityId / specifityValue I provide.
Here what I'm trying to do:
// The list of couple SpecifityID (color, material..) / SpecifityValue (red, yellow, wood...)
List<string> SpecId_SpecValue = new List<string>();
SpecId_SpecValue.Add("3535a444-1139-4a1e-989f-795eb9be43be_BEA");
SpecId_SpecValue.Add("35ad6162-a885-4a6a-8044-78b68f6b2c4b_Purple");
int filterCOunt = SpecId_SpecValue.Count;
var query =
Products
.Include(pd => pd.ProductsSpecifity)
.Where(z => SpecId_SpecValue
.Intersect(z.ProductsSpecifity.Select(x => (x.SpecifityID.ToString() + "_" + x.SpecifityValue)).ToList()).Count() == filterCOunt);
I got the error : InvalidOperationException: The LINQ expression 'DbSet() could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. which mean it can't be translated to SQL and I need to ToList before my filter.
The problem is, I don't want to call ToList() because I got huge number of products in my Database and I don't want to load them in memory before filtering them.
Is there an other way to achieve what I need to do?
I ended up using a solution found in the link #Gert Arnold provide here.
I used BlazarTech.QueryableValues.SqlServer #yv989c's answers
Here's what is now working like a charm :
// The list of couple SpecifityID (color, material..) / SpecifityValue (red, yellow, wood...)
Dictionary<Guid, string> SpecId_SpecValue = new Dictionary<Guid, string>();
SpecId_SpecValue.Add(new Guid("3535a444-1139-4a1e-989f-795eb9be43be"), "BEA");
SpecId_SpecValue.Add(new Guid("35ad6162-a885-4a6a-8044-78b68f6b2c4b"), "Purple");
// BlazarTech.QueryableValues.SqlServer
var queryableValues = DbContext.AsQueryableValues(SpecId_SpecValue);
var query = Products.Include(pd => pd.ProductsSpecifity)
.Where(x => x.ProductsSpecifity
.Where(e => queryableValues
.Where(v =>
v.Key == e.SpecifityID &&
v.Value == e.SpecifityValue
)
.Any()
).Count() == dynamicFilter.Count);
The query expresses "products of which all x.SpecifityID.ToString() + "_" + x.SpecifityValue combinations exactly match some given combinations".
Set combination operators like Except often don't play nice with EF for various reasons I'm not going into here. Fortunately, in many of these cases a work-around can be found by using Contains, which EF does support well. In your case:
var query = Products.Include(pd => pd.ProductsSpecifity)
.Where(z => z.ProductsSpecifity
.Select(x => x.SpecifityID.ToString() + "_" + x.SpecifityValue)
.Count(s => SpecId_SpecValue.Contains(s)) == filterCount);
Please note that the comparison is not efficient. Transforming database values before comparison disables any use of indexes (is not sargable). But doing this more efficiently isn't trivial in EF, see this.
I am trying to write a method to fetch the categories and their respective products from the Northwind database and to then use xml serialization to write to a file.
I have tried the following code but get the error detailed in the heading. (The file is created but no XML is written to it).
Is anyone able to advise what is wrong with my code ? Any assistance would be greatly appreciated. Thank you.
static async void SerializeCategoriesWithXML() {
FileStream xmlFileStream = null;
XmlWriter xml = null;
// Create file to write to :
string path = Combine(CurrentDirectory, "CategoriesAndTheirProducts.xml");
// Create a file stream :
xmlFileStream = File.Create(path);
// Wrap the file stream in an Xml writer helper and automatically indent the nested elements :
xml = XmlWriter.Create(xmlFileStream, new XmlWriterSettings { Indent = true });
using (var db = new NorthwindContext())
{
// A query to get all categories and their related products :
IQueryable<Categories> cats = db.Categories
.Include(c => c.Products
.ToList());
await using (FileStream stream = File.Create(path))
{
// Write the Xml declaration :
xml.WriteStartDocument();
// Serialize the object graph to the stream :
foreach (Categories c in cats)
{
// Write a root element :
xml.WriteStartElement("category");
foreach(Products p in c.Products)
{
xml.WriteElementString("product", p.ProductName);
}
// Write the closing root element :
xml.WriteEndElement();
xml.Flush();
}
// CLose the helper and stream :
xml.Close();
xmlFileStream.Close();
}
}
}
There are multiple problems with the code being shared.
Lets try to understand each problem and think of a possible solution to the problem -
The problem statement as far as I understand is, you wanted to create a XML file with category and products under the category. So for simplicity I assume you are trying to get a XML file as below -
<?xml version="1.0" encoding="utf-8"?>
<categories>
<category>
<product>Chai</product>
<product>Chang</product>
<product>Guaraná Fantástica</product>
<product>Sasquatch Ale</product>
<product>Steeleye Stout</product>
<product>Côte de Blaye</product>
</category>
<category>
<product>Aniseed Syrup</product>
<product>Chef Anton's Cajun Seasoning</product>
<product>Chef Anton's Gumbo Mix</product>
<product>Grandma's Boysenberry Spread</product>
</category>
</categories>
Coming to what's wrong with above posted code -
Problem 1: Multiple Creation of file with the specified path -
// Create a file stream :
xmlFileStream = File.Create(path);
you have already fired File.Create in the above line so when you are firing the below code it is saying file already in use.... (the below line is not required)
await using (FileStream stream = File.Create(path))
Problem 2: Linq query is not right. You can replace your linq query with the below code -
var cats = db.Categories
.Include(c => c.Products).ToList();
Problem 3: Xml construction is wrong...
You need to wrap the category tag inside a parent as multiple category objects will get created. Also in the code above you are trying to flush the xml when one category is read. You need to perform flush once the last
xml.WriteEndElement();
is executed.
So you can replace the code block for creating xml as below -
// Write the Xml declaration :
xml.WriteStartDocument();
xml.WriteStartElement("categories");
// Serialize the object graph to the stream :
foreach (Categories c in cats)
{
// Write a root element :
xml.WriteStartElement("category");
foreach (Products p in c.Products)
{
xml.WriteElementString("product", p.ProductName);
}
// Write the closing root element :
xml.WriteEndElement();
}
xml.WriteEndElement();
xml.Flush();
// CLose the helper and stream :
xml.Close();
xmlFileStream.Close();
Now the file should get created with the categories->category[].
And each category->product[].
Thanks
Because OOXML documents don't seem to follow proper XML rules, a Bookmark consists of a BookmarkStart, a BookmarkEnd and an arbitrary number of elements in-between; not a hierarchy but a single flow of elements which have to be traversed in the right order:
<w:bookmarkStart w:id="4" w:name="Author"/>
<w:r w:rsidR="009878B3"><w:rPr><w:sz w:val="28"/></w:rPr><w:t><</w:t></w:r>
<w:r w:rsidR="005E0909"><w:rPr><w:sz w:val="28"/></w:rPr><w:t xml:space="preserve"> </w:t></w:r>
<w:r w:rsidR="009878B3"><w:rPr><w:sz w:val="28"/></w:rPr><w:t>Author></w:t></w:r>
<w:bookmarkEnd w:id="4"/>
I already ran into this problem in a related question: https://stackoverflow.com/questions/28219201/how-to-get-the-text-of-a-bookmark-as-a-single-string
But this question is, how can I remove the Bookmark entirely from the document, without breaking anything? Do I have to iterate through siblings from the BookmarkStart until I reach a BookmarkEnd? Is there some useful API method which makes up for the failure to use XML properly whereby one would just have a Bookmark node which could be deleted?!
You can just remove the BookmarkStart via the API and it will remove the corresponding BookmarkEnd for you and leave any contents intact. In C# something like this should work:
public static void RemoveBookmark(string filename, string bookmarkName)
{
using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true))
{
Body body = wordDocument.MainDocumentPart.Document.Body;
//find a matching BookmarkStart based on name
BookmarkStart start = body.Descendants<BookmarkStart>().FirstOrDefault(b => b.Name == bookmarkName);
if (start == null)
{
throw new Exception(string.Format("Bookmark {0} not found", bookmarkName));
}
//this is clever enough to remove the BookmarkStart and BookmarkEnd
start.Remove();
wordDocument.MainDocumentPart.Document.Save();
}
}
I am trying to add the team names(to be fetched from mongoDB) in the form to let user select the form name.
I am not getting how to add the database fetched form names in the dropdown list.
It should search based on organization_id first & then form_name.
what i am doing is this:
<?= $form->field($model1, 'form_name')->dropDownList(ArrayHelper::map(CreateTeam::find(array('organization_id' => Yii::$app->session['organization_id']))->all(), 'form_name')); ?>
It is showing me an error that missing the third argument. What could be the third argument in that case???
I went through issue with fetching record from country collection to serve record in state form
I got solution as below (considering as my state form)
use app\models\Countries;
use yii\helpers\ArrayHelper;
$countries=Countries::find()->all();
$listData=ArrayHelper::map(Countries::find()->all(),function ($model){return (string)$model->_id;},'name');
echo $form->field($model, 'countries_id')->dropDownList($listData, ['prompt'=>'Select...']);
Hope I was able to let you understand !
$collection2 = Yii::$app->mongodb->getCollection('teamdashboard');
$models = $collection2->find(array('organization_id' => Yii::$app- >session['organization_id']));
$items = ArrayHelper::getColumn($models, 'team_name');
return $this->render('teamdashboard', ['model1' => $model1, 'model2' => $model2, 'items' => $items]);
This one works fine for mongodb...
Yes, in ArrayHelper::map() first three parameters are required, so you are definitely calling this method wrong.
Check out official documentation for map().
The second parameter represents the actual values in database (usually they are primary keys), the third - readable text.
Assuming your primary key is id it should be:
$models = CreateTeam::find([
'organization_id' => Yii::$app->session['organization_id'],
])->all();
$items = ArrayHelper::map($models, 'id', 'form_name');
<?= $form->field($model1, 'form_name')->dropDownList($items) ?>
May be this is a basic question, but I have trouble binding the OData count in XML view.
In the following example, I want to bind the count of products from the OData model.
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{path : 'Products/$count'}"
numberUnit="Products"/>
</List>
Each category needs to display count of products in the respective category as in
/Categories(1)/Products/$count
/Categories(2)/Products/$count
I had a similar issue. Although I am not thrilled with my solution, it uses expression binding and works without the need for a separate formatter:
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{= ${Products}.length }"
numberUnit="Products" />
</List>
Like #Jasper_07, you still need to include Products in the expand, but you are ignoring most of the data coming back.
I dont think its currently possible
- $count is an OData query option, the equivalent in ODataListBinding is length, eg Products.length I cant think of a way to bind to it
you can achieve the count in a couple of ways using a formatter
option 1 - the simplest, create a list binding which reads the total number of products, it does a synchronous call and returns only the $count
function productCount(oValue) {
//return the number of products linked to Category // sync call only to get $count
if (oValue) {
var sPath = this.getBindingContext().getPath() + '/Products';
var oBindings = this.getModel().bindList(sPath);
return oBindings.getLength();
}
};
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{path : 'CategoryName',formatter:'productCount'}"
numberUnit="Products"
</ObjectListItem>
</List>
option 2 - use an expand and return a very small set of data, in this case only CategoryName and ProductID, the caveat here is whether you have to by pass table paging to get full list
function productCount(oValue) {
//read the number of products returned
if (oValue) {
return oValue.length;
}
};
<List items="{/Categories,parameters:{expand:'Products', select:'CategoryName,Products/ProductID'}}">
<ObjectListItem
title="{CategoryName}"
number="{path : 'Products',formatter:'productCount'}"
numberUnit="Products"
</ObjectListItem>
</List>
Well.. I had exactly the same requirement and didn't want to perform the clever solution from #jasper as it will load all Products collection from the oData service.
This was the way I solve it:
View
Use a controller
Give your list an ID
Use a function on list's updateFinished event.
<mvc:View
controllerName="view.Root"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
>
<List id="list"
headerText="Categories"
items="{/Categories}"
growing="true"
growingThreshold="4"
growingScrollToLoad="true"
updateFinished=".countProducts"
>
<ObjectListItem
title="{description}"
numberUnit="Products"
/>
</List>
</mvc:View>
Controller
Implement countProducts function
Use jQuery to request the $count for each list item - Notice how the URL is generated concatenating model's service URL with the item's binding context
As jQuery uses asynchronous requests, by the time you get the first response, your for will be finished. So it can use IIFE to avoid filling just the last list item with your AJAX response
countProducts: function(e){
var m = sap.ui.getCore().getModel();
var items = this.byId("list").getItems();
for (var item_index = 0; item_index < items.length; item_index++) {
var item = items[item_index];
(function(_item) {
$.get(
m.sServiceUrl + _item.getBindingContextPath() + "/Categorias/$count",
function(count) {
_item.setNumber(count);
}
);
})(item);
}
}
I´d another solution using Manifest.json, Component.js and Controller.js for similar Issue.
First, I defined the Id in App.view.xml, for example:
<Title id="titleId" text="" level="H2"/>
After, I check Manifest.json, in especial:
{
"sap.app": {
"dataSources": {
"AXXX": {
"uri": "https://cors-anywhere.herokuapp.com/https://services.odata.org/Northwind/Northwind.svc/",
Next, in Componente.js at init:function() I put:
var oDataServiceUrl = this.getMetadata().getManifestEntry("sap.app").dataSources["AXXX"].uri;
console.log("oDataServiceUrl = ", oDataServiceUrl);
localStorage.setItem('oDataServiceUrl', oDataServiceUrl);
This code read Manifest.json and get Url to oDataService called AXXX.
Finnaly, I created one function in App Controller, such as:
countCustomersInAXXX : function (oEvent) {
var suffix = 'Customers/$count';
var oDataServiceUrl = localStorage.getItem('oDataServiceUrl');
var oDataServiceUri = oDataServiceUrl.concat(suffix);
console.log('App.controller.js: oDataServiceUri', oDataServiceUri);
var count = $.ajax({type: "GET", url: oDataServiceUri, async: false}).responseText;
console.log('App.controller.js: countCustomersInAXXX:' , count);
this.getView().byId("titleId").setText(count);
}
This code get the quantity of Customers and set the value in titleId.
To start this process you can user a button or one event, in my case I use this Table property:
updateFinished="countCustomersInAXXX"