Creating lists from XML with optional elements - c#

I am working on some XML documents and trying to parse them into Lists.
I have read the following answers and articles but, I couldn't find a way to parse optional "Values" tag and it's elements for the code below.
http://blogs.msdn.com/b/ericwhite/archive/2009/05/14/working-with-optional-elements-and-attributes-in-linq-to-xml-queries.aspx ,
LINQ to XML optional element query ,
Can XML with optional elements be used with LINQ and Anonymous Types? ,
How to Parse Optional or Null XML Elements & Attributes with LINQ?
The code:
XDocument xdoc1 = XDocument.Load("sample.xml");
List<Message> messages = new List<Message>();
messages = (from _message in xdoc1.Element("Messages").Elements("Message")
select new Message
{
Id = _message.Element("Id").Value,
Alias = _message.Element("Alias").Value,
Fields = (from _field in _message.Element("Fields").Elements("Field")
select new Field
{
FieldName = _field.Element("FieldName").Value,
StartIndex = Convert.ToInt16(_field.Element("StartIndex").Value),
StopIndex = Convert.ToInt16(_field.Element("StopIndex").Value),
DefaultBits = _field.Element("DefaultBits").Value,
CalculationMethod = (CalculationMethods.Types)Enum.Parse(typeof(CalculationMethods.Types), _field.Element("CalculationMethod").Value),
Values = (from _value in _field.Element("Values").Elements("Value")
select new Value
{
Bits = _value.Element("Bits").Value,
Meaning = _value.Element("Meaning").Value
}).ToList()
}).ToList()
}).ToList();
Some "Fields" has "Field"s with "FieldName", "StartIndex", "StopIndex", "DefaultBits", "CalculationMethod" and optionally "Values". How can I fix the code for optional "Values" tags in XML?

Instead of using .Value, use the explicit conversions in XElement. So for example:
FieldName = (string) _field.Element("FieldName"),
StartIndex = (short?) _field.Element("StartIndex")
You'll end up with null values for absent elements, because that's what the custom conversions on XElement do when the operand is null (and the return type is a reference type or a nullable value type).
For Values, it's even easier - you just need to change _field.Element("Values") to _field.Elements("Values"):
Values = (from _value in _field.Elements("Values").Elements("Value")
select new Value
{
Bits = _value.Element("Bits").Value,
Meaning = _value.Element("Meaning").Value
}).ToList()
That way, _field.Elements("Values") will return an empty sequence if there's no Values element.

you can use the conditional oprator in linq.
FieldName = _field.Element("FieldName")!=null?_field.Element("FieldName").Value:string.Empty;

Related

Create same LINQ anonymous type from different XML element

I have an XML file like this:-
Notice that each <Field></Field> can have different element like the highlighted <I32> or <String>. I want to show the element name in a datagridview like this which Type is for the name of element (either I32 or String or other child element of <Field>) :-
So far, I've tried this code but it return An unhandled exception of type 'System.NullReferenceException'.
XDocument doc = XDocument.Load("GetLotDetails.xml");
var data = doc.Descendants("Document").Where(x => (String)x.Attribute("name") == "DATA").SelectMany(x => x.Elements("Field"));
var query = from d in data
let str = d.Element("String").Name
let other = d.Element("I32").Name
select new
{
Name = d.Attribute("name").Value,
Type = str.Equals("String") ? "String" : (other.Equals("I32") ? "I32" : null),
Value = d.Value,
};
dataGridView1.DataSource = query.ToList();
So the idea is to let the anonymous Type = *whatever element name under field*. How can I extracted different name of element in the LINQ select statement and give it to the same unknown type variable?
It has nothing to do with anonymous types. You are missing a null check
var query =
from d in data
let element = d.Element("String") ?? d.Element("I32")
select new
{
Name = d.Attribute("name").Value,
Type = element?.Name,
d.Value
};
In your original query, you unconditionally read the Name from both possible nodes, but for any given d, one of the nodes will be null. I could have written it using the null conditional operator, d.Element("String")?.Name, but the above is more readable in this context as the additional projection in your original query adds noise and potential confusion.

LINQ need to parse JSON out of a column

In a table, Leads, there is a column Data that contains a JSON string. In a LINQ statement, I need to extract a field from that JSON:
var results = from l in leads
select new MyLeadObject
{
LeadID = l.LeadID,
...
RequestType = (string)l.Data["RequestTypeID"]
};
Here's a shortened version of the JSON:
{
"RequestTypeID":1
}
RequestTypeID is a string.
I've been reading other threads and trying to cobble this together. Not having much luck.
EDIT:
With help from Nkosi, I got this far:
RequestType = (string)JSONNetSerialization.DeserializeJsonNet<LeadData>(l.Data).RequestTypeID
The only problem is that LeadData.RequestTypeID is an enum, so it won't convert the enum to a string. I'm not sure how to get the value of the enum instead of the entire enum itself. Outside of LINQ I could do this: RequestTypeID.GetDisplayName(); but .GetDisplayName() is not recognized by LINQ.
You can use Json.Net to parse the JSON in Data field to get the property.
var results = leads.Select(l =>
new MyLeadObject {
LeadID = l.LeadID,
//...
RequestType = (string)JsonConvert.DeserializeObject(l.Data)["RequestTypeID"]
});

Handle null element in linq

I'm reading data from an XML. I'm running into an issue where a value is null and I'm not sure the best way to handle it. Below is a snippet of code. The Street Address 2 does not exist in this XML but does in others so I need to make it dynamic enough to handle both instances.
var storeInfo = storeRows.Descendants("Store").Select(s => new
{
storeName = s.Element("StoreName").Value,
streetAddress1 = s.Element("StreetAddress1").Value,
streetAddress2 = s.Element("StreetAddress2").Value
});
{
foreach (var st in storeInfo)
{
alStoreName.Add(st.storeName.ToString());
alStreet1.Add(st.StreetAddress1.ToString());
alStreet2.Add(st.StreetAddress2.ToString());
}
}
Use explicit cast instead of accessing Value property
var storeInfo = storeRows.Descendants("Store").Select(s => new
{
storeName = (string)s.Element("StoreName"),
streetAddress1 = (string)s.Element("StreetAddress1"),
streetAddress2 = (string)s.Element("StreetAddress2")
});
This will return null if the element does not exist.
In addition I recommend you to create a class to encapsualate store info instead of storing information in different lists. Then just have a list of storeInfo's instead of a list of anonymous type:
var storeInfo = storeRows.Descendants("Store").Select(s => new StoreInfo
{
storeName = (string)s.Element("StoreName"),
streetAddress1 = (string)s.Element("StreetAddress1"),
streetAddress2 = (string)s.Element("StreetAddress2")
});
You should use (string)XElement explicit cast instead of Value property. It will return null when element doesn't exist.
streetAddress2 = (string)s.Element("StreetAddress2")
You can cast XElement to most of the primitive types, string, DateTime, Guid and Nullable<T> of above. See full list here: XElement Type Conversions
The same rule applies to XAttribute.

How do I create a list object that contains a list of another object type using from within a query using linq and c#

I am trying to return a list of an object that contains another object list as a databmember using linq. I've tried the examples shown but I keep getting ad different error with each attempt. One of which is as follows: LINQ to Entities does not recognize the method 'System.Collections.Generic.List1[SunGard.Tools.Notifications.LinkVariable] ToList[LinkVariable](System.Collections.Generic.IEnumerable1[SunGard.Tools.Notifications.LinkVariable])' method, and this method cannot be translated into a store expression.
I have an object (AlertMessageReturn) that contains some string datamembers as well as a list aof another object (List). I have a class that defines the LinkVarible and a table that contains the values. My query looks like this:
AlertMessagesQuery = from alertMessage in this.context.AlertMessages
where alertMessage.UserId=UserId
select new AlertMessageReturn()
{ PAM_ShortMessage = alertMessage.PAM_ShortMessage,
PAM_LongMessage = alertMessage.PAM_LongMessage,
PAM_LongMessageRemote = alertMessage.PAM_LongMessageRemote,
LinkVariables = (from linkVariable in this.context.AlertMessageLinks
from user in this.context.AlertMessageUsers
where user.PAMU_PAM_ID == linkVariable.PAML_PAM_ID && user.PAMU_UserId == UserId
select new LinkVariable()
{
Name = linkVariable.PAML_SessionVariableName,
Value = linkVariable.PAML_SessionVariableValue
})
};
The error is related to the type returned for linkvariables.
Please help.
I changed the code as follows:
LinkDataQuery = from linkData in this.context.AlertMessageLinks
from user1 in this.context.AlertMessageUsers
where user1.PAMU_PAM_ID == linkData.PAML_PAM_ID && user1.PAMU_UserId == UserId
select new LinkData
{
Name = linkData.PAML_SessionVariableName,
Value = linkData.PAML_SessionVariableValue
};
var links = LinkDataQuery.ToList();
AlertMessagesQuery = from alertMessage in this.context.AlertMessages
where alertMessage.UserId=UserId
select new AlertMessageReturn()
{ PAM_ShortMessage = alertMessage.PAM_ShortMessage,
PAM_LongMessage = alertMessage.PAM_LongMessage,
PAM_LongMessageRemote = alertMessage.PAM_LongMessageRemote,
LinkVariables = links
};
var AlertMessages = AlertMessagesQuery.ToList(); // this is where the error point to
if (AlertMessages.Any())
{
return AlertMessages;
}
The error I now get is:System.NotSupportedException: Unable to create a constant value of type 'SunGard.Tools.Notifications.LinkData'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
The LINQ to SQL engine cannot turn your sub-query to generate the LinkVariables into SQL. More importantly, SQL cannot return nested data sets like that.
Any time you get a message of type 'cannot be translated into a store expression' it is an indicator that you are doing something with your linq that is attempting to be translated into other statements (usually SQL). For example, if you say
....select new MyObject
{
Id = Guid.Parse( passedIdentity ),
....
}
while this is a totally valid C# statement you will get an error that Guid.Parse cannot be handled by linq. If it is possible to move the variables into external variables that are used inside the query then it would work. So you would do...
string name = linkVariable.PAML_SessionVariableName;
string nValue = ....
....
select New LinkVariable
{
Name=name,
Value=nValue
};
Also ... you do not need the closing parens on the Select New statement.
While LINQ to SQL can bring back object heirarchies, it can't project into types that aren't part of the model. Instead of projecting into the AlertMessageReturn type, try projecting into an anonymous type in the IQueryable portion of the code. Once you're done structuring your database query, force the results to come back (using AsEnumerable) and then project that into your AlertMessageReturn type. It's more overhead, but does work. Alternatively, you can use something like AutoMapper to translate your anonymous type into the result types.
AlertMessagesQuery =
from alertMessage in this.context.AlertMessages
where alertMessage.UserId=UserId
select new
{
alertMessage.PAM_ShortMessage,
alertMessage.PAM_LongMessage,
alertMessage.PAM_LongMessageRemote,
LinkVariables = from linkVariable in this.context.AlertMessageLinks
from user in this.context.AlertMessageUsers
where user.PAMU_PAM_ID == linkVariable.PAML_PAM_ID && user.PAMU_UserId == UserId
select new
{
Name = linkVariable.PAML_SessionVariableName,
Value = linkVariable.PAML_SessionVariableValue
})
};
var alertMessageResults =
from message in AlertMessagesQuery.AsEnumerable()
select new AlertMessageResult
{
PAM_ShortMessage = mesage.PAM_ShortMessage,
PAM_LongMessage = message.PAM_LongMessage,
PAM_LongMessageRemote = message.PAM_LongMessageRemote,
LinkVariables = (from variable in message.LinkVariables
select new LinkVariable { Name=variable.Name, Value = variable.Value})
.ToList()
};
return alertMessageResults.ToList();

Linq / XML - How do you handle non existing nodes?

I am trying to figure out how to handle nodes that do not exist for all of my "card" elements. I have the following linq query:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = deck.Attribute("name").Value,
Image = deck.Element("Image").Attribute("path").Value,
Usage = (int)deck.Element("Usage"),
Type = deck.Element("Type").Value,
Strength = (int)deck.Element("Ability") ?? 0
}).ToList();
with the Strength item, I had read another posting that the ?? handles the null. I am getting the following error though:
Operator '??' cannot be applied to operands of type 'int' and 'int'
How do I handle this issue?
Thanks!
Rather than use the Value property, cast to string... and for the int, cast to int? instead. The user defined conversions to nullable types will return null if the source XAttribute/XElement is null:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = (string) deck.Attribute("name"),
Image = (string) deck.Element("Image").Attribute("path"),
Usage = (int?) deck.Element("Usage"),
Type = (string) deck.Element("Type"),
Strength = (int?) deck.Element("Ability") ?? 0
}).ToList();
Note that this won't help for the case where the Image element is missing, as then it'll try to dereference a null element to find the path attribute. Let me know if you want a workaround for that, but it'll be a bit of a pain, relatively speaking.
EDIT: You can always create an extension method for this yourself:
public static XAttribute NullSafeAttribute(this XElement element, XName name)
{
return element == null ? null : element.Attribute(name);
}
Then call it like this:
Image = (string) deck.Element("Image").NullSafeAttribute("path"),

Categories

Resources