Trying to understand how to conditionally write out element/attributes with LIINQToXML - linq

This is the first time that I created a XML document using LINQToXML.
I am trying to understand how can I conditionally create attributes(or elements) when creating my document?
In this example a given car may/may not have a feature to it, so in that case I would not want to create that element, I also may have certain attributes in the feature node that could be missing. How could I handle these scenarios?
XDocument xDoc = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car", new XAttribute("name", p.CarName),
new XElement("Feature", new XAttribute("door", p.Door), new XAttribute("model", p.Model))
)
)
)
);
Desired result #1 (All features are missing for a given car):
<root>
<NodeA />
<Cars>
<Car name="Honda">
<Feature door="4" model="Accord" />
</Car>
<Car name="Ford" />
</Cars>
</root>
Desired result #2 (Some features could exist)
<root>
<NodeA />
<Cars>
<Car name="Honda">
<Feature door="4" model="Accord" />
</Car>
<Car name="Ford">
<Feature model="Focus" />
</Car>
</Cars>
</root>

2 seperate solutions in here. Either use a method to create the features node, or do it all in one:
static void Main(string[] args)
{
var listCars = new List<Car>();
listCars.Add(new Car { CarName = "test 1", Door = "0", Model = "" });
listCars.Add(new Car { CarName = "test 2", Door = "", Model = "" });
listCars.Add(new Car { CarName = "test 3", Door = "0", Model = "0" });
XDocument xDoc2 = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car",
new XAttribute("name", p.CarName),
p.Door != "" || p.Model != "" ?
new XElement("Feature",
p.Door != "" ? new XAttribute("door", p.Door) : null,
p.Model != "" ? new XAttribute("model", p.Model) : null) : null
)
)
)
);
XDocument xDoc = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car",
new XAttribute("name", p.CarName),
CreateFeature(p)
)
)
)
);
}
static XElement CreateFeature(Car p)
{
var el = new XElement("Feature",
p.Door != "" ? new XAttribute("door", p.Door) : null,
p.Model != "" ? new XAttribute("model", p.Model) : null);
return !el.Attributes().Any() ? null : el;
}

If you supply null instead of an element, it will be ignored, so you can use constructs like the following.
p.CarName != null ? new XAttribute("name", p.CarName) : null
If you're using C# 6, you can use null propagation.

Related

How to conditionally read elements in LINQToXML

How would I go about creating my desired list by using LINQToXml?
I am close with both attempts so far. I should be able to do this without creating to separate queries right?
This is my XML:
<main>
<cars>
<car name="Honda">
<feature door="4" name="Accord" />
<feature door="2" name="Civic"/>
<feature door="4" name="CRV"/>
</car>
<car name="Ford"/>
<car name="Kia"/>
<car name="Subaru">
<feature door="4" name="Outback"/>
<feature door="4" name="Legacy"/>
</car>
</cars>
</main>
Attempt #1
This will return the first car with feature.
var listCars = (from c in doc.Root.Descendants("cars")
select new Car
{
Model = (p.Element("car") != null) ? p.Element("car").Attribute("name").Value : null,
Door = (p.Element("car") != null) ? p.Element("car").Attribute("door").Value : null,
Name = p.Attribute("name").Value
}).ToList();
Attempt #2
This will return all of the cars that have features
Ford and Kia would be missing
var cars = from c in doc.Root.Descendants("cars")
select c;
var listPermissions = (from c in cars.Descendants("car")
let cName = p.Parent.Attribute("name").Value
select new Car
{
Model = p.Attribute("name").Value,
Door = p.Attribute("door").Value,
Name = pgn
}).ToList();
What I am trying to do is to create a list of cars that look like:
Honda, 4, Accord
Honda, 2, Civic
Honda, 4, CRV
Ford, null, null
Kia, null, null
Subaru, 4, Outback
Subaru, 4, Legacy
You can do this by using a LEFT JOIN in LINQ as outlined on 101 LINQ Samples.
var makes =
(from doc in document.Root.Descendants("cars").Descendants("car")
join f in document.Root.Descendants("cars").Descendants("car").Descendants("feature")
on doc.Attribute("name").Value.ToLowerInvariant() equals f.Parent.Attribute("name").Value.ToLowerInvariant() into ps
from f in ps.DefaultIfEmpty()
select new
{
Model = doc.Attribute("name").Value,
Door = f == null ? string.Empty : f.Attribute("door").Value,
Name = f == null ? string.Empty : f.Attribute("name").Value
})
.ToList();

How to find XElement specific Children (in c# with LINQ)?

I've got an XML file like this :
<root>
<environment env="PROD">
<key name="Big Key" propagate="true" value="21" />
</environment>
<environment env="PRE-PROD">
<key name="First Key" propagate="true" value="4" />
<key name="Second Key" propagate="true" value="3" />
</environment>
</root>
I want to check if a key exist in that file, and if the propagate item is true.
I manage to get those 2 System.Xml.Linq.XElement : key name="First Key" AND key name="Second Key".
but I would like to get only the one by pKeyname (like "Second Key" for eg.) I can't find how...
public static bool IsPropagate(string pXmlFileName, string pEnvironment, string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
IEnumerable<XElement> childList = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements();
if (childList.Any())
return true;
return false;
}
Any help would be highly appreciated!
This would help to get the exact key:
public static bool IsPropagate(string pXmlFileName, string pEnvironment,
string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
IEnumerable<XElement> childList = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements()
.Where(a => a.Attribute("name").Value == pKeyname);
if (childList.Any())
return true;
return false;
}
It's working by adding the "FirstOrDefault"! Thanks.
public static bool IsPropagate(string pXmlFileName, string pEnvironment, string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
XElement child = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements()
.FirstOrDefault(a => a.Attribute("name").Value == pKeyname);
if (child != null)
return true;
return false;
}

Can I 'flatten' an XDocument with Linq?

I have a heavily nested XML document that I need to load into my db for additional processing. For various reasons beyond the scope of this discussion I need to 'flatten' that structure down, then load it into a DataTables and then I can SQLBulkCopy it into the db where it will get processed. So assume my original XML looks something like this (mine is even more heavily nested, but this is the basic idea):
<data>
<report id="1234" name="XYZ">
<department id="234" name="Accounting">
<item id="ABCD" name="some item">
<detail id="detail1" value="1"/>
<detail id="detail2" value="2"/>
<detail id="detail3" value="3"/>
</item>
</department>
</report>
</data>
and I want to flatten that down into a single (albeit redundant) table structure where each attribute becomes a column (i.e. ReportId, ReportName, DepartmentId, DepartmentName, ItemId, ItemName, Detail1, Detail2, Detail3).
So my question is simply 'is it possible to accomplish this with a simple Linq query'? In the past I would just write some XSLT and be done with it but I'm curious if the Linq library can accomplish the same thing?
thanks!
Is this what you're looking for?
var doc = XDocument.Load(fileName);
var details =
from report in doc.Root.Elements("report")
from department in report.Elements("department")
from item in department.Elements("item")
from detail in item.Elements("detail")
select new
{
ReportId = (int)report.Attribute("id"),
ReportName = (string)report.Attribute("name"),
DepartmentId = (int)department.Attribute("id"),
DepartmentName = (string)department.Attribute("name"),
ItemId = (string)item.Attribute("id"),
ItemName = (string)item.Attribute("name"),
DetailId = (string)detail.Attribute("id"),
DetailValue = (int)detail.Attribute("value"),
};
If you want it as a DataTable, you can use the following extension method:
public static DataTable ToDataTable<T>(this IEnumerable<T> source)
{
PropertyInfo[] properties = typeof(T).GetProperties()
.Where(p => p.CanRead && !p.GetIndexParameters().Any())
.ToArray();
DataTable table = new DataTable();
foreach (var p in properties)
{
Type type = p.PropertyType;
bool allowNull = !type.IsValueType;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
allowNull = true;
type = Nullable.GetUnderlyingType(type);
}
DataColumn column = table.Columns.Add(p.Name, type);
column.AllowDBNull = allowNull;
column.ReadOnly = !p.CanWrite;
}
foreach (var item in source)
{
DataRow row = table.NewRow();
foreach (var p in properties)
{
object value = p.GetValue(item, null) ?? DBNull.Value;
row[p.Name] = value;
}
table.Rows.Add(row);
}
return table;
}
Use it like this:
var table = details.CopyToDataTable();

LINQ Union with Constant Values

Very primitive question but I am stuck (I guess being newbie). I have a function which is supposed to send me the list of companies : ALSO, I want the caller to be able to specify a top element for the drop-down list as well.. (say for "None"). I have following piece of code, how I will append the Top Element with the returning SelectList?
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new
{
c.Id,
c.Company
};
return new SelectList( cs.AsEnumerable(), "Id", "Comapny" );
}
Thanks!
This should work for you:
List<Corporate> corporates =
(from c in entityDataContext.Corporates
where (c.Approved == onlyApproved || onlyApproved == false)
select c).ToList();
corporates.Add(new Corporate { Id = -1, Company = "None" });
return new SelectList(corporates.AsEnumerable(), "Id", "Comapny");
This method has always worked for me.
public static SelectList GetCompanies( bool onlyApproved, FCCIEntityDataContext entityDataContext, SelectListItem TopElement )
{
var cs = from c in entityDataContext.Corporates
where ( c.Approved == onlyApproved || onlyApproved == false )
select new SelectListItem {
Value = c.Id,
Text = c.Company
};
var list = cs.ToList();
list.Insert(0, TopElement);
var selectList = new SelectList( list, "Value", "Text" );
selectList.SelectedValue = TopElement.Value;
return selectList;
}
Update forgot the lesson I learned when I did this. You have to output the LINQ as SelectListItem.
cs.ToList().Insert(0, new { TopElement.ID, TopElement.Company });
You could convert it to a list as indicated or you could union the IQueryable result with a constant array of one element (and even sort it):
static void Main(string[] args)
{
var sampleData = new[] {
new { Id = 1, Company = "Acme", Approved = true },
new { Id = 2, Company = "Blah", Approved = true }
};
bool onlyApproved = true;
var cs = from c in sampleData
where (c.Approved == onlyApproved || onlyApproved == false)
select new
{
c.Id,
c.Company
};
cs = cs.Union(new [] {new { Id = -1, Company = "None" }}).OrderBy(c => c.Id);
foreach (var c in cs)
{
Console.WriteLine(String.Format("Id = {0}; Company = {1}", c.Id, c.Company));
}
Console.ReadKey();
}

Need to get the sibling value

I need to create a cache using an XML file. Here's the method that I will be using. I want this method to return the id based on the key-product_name. So I want it to create a cache one time programmatically and then only if the key is not found then create a [new entry in the] cache. If everything looks okay the problem is getting the id of product. Please advise. I have included the code and xml file.
public static string getProductId(string product_name)
public static string getTechId(string fieldName)
{
Cache cache = HttpContext.Current.Cache; //neeed to change this.
string cacheNameEpm = product_name + "PrdName";
if (cache[cacheNameEpm] == null)
{
XPathDocument doc = new XPathDocument(HttpContext.Current.Request.MapPath("inc/xml/prd.xml"));
XPathNavigator navigator = doc.CreateNavigator();
string selectName = "/Products/Product/ProductName";
XPathNodeIterator nodes = navigator.Select(selectName);
while (nodes.MoveNext())
{
switch (nodes.Current.Name)
{
case "ProductName":
cacheNameEpm = nodes.Current.Value + "PrdName";
navigator.Select("/Products/Product/ProductId");
navigator.MoveToNext();
if (nodes.Current.Name == "ProductId")
{
id = navigator.Value;
}
cache.Add(cacheNameEpm, id, null, DateTime.Now + new TimeSpan(4, 0, 0), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null);
break;
}
}
}
return cache[cacheNameEpm] as string;
}
Here is the xml file:
<Products>
<Product>
<ProductName>PDPArch</ProductName>
<ProductId>57947</ProductId>
</Product>
<Product>
<ProductName>TYFTType</ProductName>
<ProductId>94384</ProductId>
</Product>
</Products>
To get the product id of, say, "TYFTType", you would need this XPath expression:
/Products/Product[ProductName = 'TYFTType']/ProductId
So what about something along the lines of (untested, but you get the idea):
public static string getProductId(string product_name)
{
Cache cache = HttpContext.Current.Cache;
string cacheNameEpm = product_name + "PrdName";
if (cache[cacheNameEpm] == null)
{
// let's cache the navigator itself as well
string cacheNameNav = "prd.xml_Navigator";
XPathNavigator navigator = cache[cacheNameNav] as XPathNavigator;
if (navigator == null)
{
XPathDocument doc = new XPathDocument(
HttpContext.Current.Request.MapPath("inc/xml/prd.xml")
);
navigator = doc.CreateNavigator();
cache.Add(
cacheNameNav,
navigator,
null,
DateTime.Now + new TimeSpan(4, 0, 0),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null
);
}
string xpath = String.Format(
"/Products/Product[ProductName = '{0}']/ProductId", product_name
);
XPathNavigator product_id = navigator.SelectSingleNode(xpath);
if (product_id.IsNode)
{
cache.Add(
cacheNameEpm,
product_id.Value,
null,
DateTime.Now + new TimeSpan(4, 0, 0),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null
);
}
}
return cache[cacheNameEpm] as string;
}
The above has two little issues:
It will not remember the non-existent product names.
Product names must not contain a single quote or the XPath expression will break. You should add a check for this condition.

Resources