Reading XML and Instantiate class at the same time - c#

I have a class called PurchaseOrderItem
public class PurchaseOrderItem
{
public Int64 PONumber { get; set; }
public string Description { get; set; }
public string UM { get; set; }
public int QTY { get; set; }
public double Cost { get; set; }
}
I'm trying to to read data from XML file by iterating sections from XML and populate data into PurchaseOrderItems which is List. But when i try the code below i'm getting an error message of "Object reference not set to an instance of an object."
I'm pretty sure i'm missing something because my poor OOP knowledge. Would someone kindly explain to me what causes this problem?
PurchaseOrderItems =
(from purchaseOrderItem in PO.Descendants("PurchaseOrder").Elements("ProductLineItem")
select new PurchaseOrderItem
{
PONumber = PONumber,
Description = purchaseOrderItem.Element("PurchaseOrder").Element("ProductLineItem").Element("comments").Value.Trim(),
QTY = Convert.ToInt16(purchaseOrderItem.Element("PurchaseOrder").Element("ProductLineItem").Element("OrderQuantity").Element("requestedQuantity").Element("ProductQuantity").Value.Trim()),
UM = purchaseOrderItem.Element("PurchaseOrder").Element("ProductLineItem").Element("GlobalProductUnitOfMeasureCode").Value.Trim(),
Cost = Convert.ToDouble(purchaseOrderItem.Element("PurchaseOrder").Element("ProductLineItem").Element("requestedUnitPrice").Element("FinancialAmount").Element("MonetaryAmount").Value.Trim()),
}).ToList<PurchaseOrderItem>();

purchaseOrderItem is already a ProductLineItem element by the time you reach your select clause - so currently you're trying to find a PurchaseOrder element within ProductLineItem, which will return null if it isn't found. I suspect you just want:
PurchaseOrderItems =
(from purchaseOrderItem in PO.Descendants("PurchaseOrder").Elements("ProductLineItem")
select new PurchaseOrderItem
{
PONumber = PONumber,
Description = purchaseOrderItem.Element("comments").Value.Trim(),
QTY = (short) purchaseOrderItem.Element("OrderQuantity")
.Element("requestedQuantity")
.Element("ProductQuantity"),
UM = purchaseOrderItem.Element("GlobalProductUnitOfMeasureCode")
.Value.Trim(),
Cost = (double) purchaseOrderItem.Element("requestedUnitPrice")
.Element("FinancialAmount")
.Element("MonetaryAmount")
}).ToList();
Notes:
Using the explicit conversions from XElement to short, double etc is a more appropriate way of performing conversions for XML data
It looks like you're using double for currency values: don't do that - use decimal instead
Also, you may want to consider creating a static method in PurchaseOrderItem which knows how to deserialize one from an XElement; I often find that a pattern of:
class Foo
{
static Foo FromXElement(XElement element) { ... }
XElement ToXElement() { ... }
}
works well.

Related

C# single list from different classes

I have 2 parts on an API that share some similarities but function differently. I am currently trying to take data from a list object of People from class B and add this data to a list of People created from Class A(hopefully explained well enough?)
The People structure in the 2 classes are actually the same:
[XmlRoot(ElementName = "people")]
public class People
{
[XmlElement(ElementName = "member")]
public List<Member> Member { get; set; }
}
[XmlRoot(ElementName = "member")]
public class Member
{
[XmlElement(ElementName = "firstName")]
public string FirstName { get; set; }
[XmlElement(ElementName = "lastName")]
public string LastName { get; set; }
[XmlAttribute(AttributeName = "memberId")]
public string MemberId { get; set; }
[XmlAttribute(AttributeName = "memberNotes")]
public string Notes { get; set; }
[XmlElement(ElementName = "departed")]
public string Departed { get; set; }
[XmlElement(ElementName = "currentPosition")]
public Name CurrentPosition { get; set; }
}
In normal operation the following code sets the People list just fine:
public People PersonData { get; set; }
...
....
var results = ApiA.People;
PersonData = results.Member; //during normal operation only one entry of member is returned
However in another operation the results returns a Larger list of member objects so I am trying to add to the same list to ensure handling later on uses single method for both operations due to the same data structure, what I am trying is as follows:
if(PersonData == null)
PersonData = new API_A.People();
var results = ApiB.People; //person data here belongs to API_B.Person
foreach (var res in results)
{
if (res?.Member != null)
{
if (PersonData == null)
{
PersonData.Member.AddRange(res.People.Member.Cast<API_A.Member>());
break;
}
else
PersonData.Member.Union(res.People.Member.Cast<API_A.Member>());
}
}
No errors are returned in the ide but during operation I continually receive a NullReferenceException during the add range operation; so as I am still learning I would really appreciate understanding what I am doing wrong here?
2 problems are obvious.
If PersonData is null, you cannot access to PersonData.Member before creating the PersonData object first. So in your case it should be:
PersonData = new People();
Next problem you'll have is the casting. Even if everything is same in 2 different classes, unless there is an inheritance relation between those, you cannot cast one to another. What you should do is to map your one class to the other. Just create a mapper method somewhere else that maps your API_A.Member to API_B.Member and/or vica versa. This kind of mapping workarounds are widely used, feel safe creating this heavy looking mapping method.
Example:
API_A.Member MapBToA(API_B.Member member)
{
return new API_A.Member {
CharacterName = member.CharacterName,
...
};
}

get specific properties within the generic list using linq

I have generic list foo as shown below
var foo = new List<XYZ>();
public class XYZ
{
public String TimeZone { get; set; }
public Decimal? B1Volume { get; set; }
public Decimal? B2Volume { get; set; }
public Decimal? B3Volume { get; set; }
public Decimal? B4Volume { get; set; }
public Decimal? B5Volume { get; set; }
// .............
// .............
public Decimal? B24Volume { get; set; }
public String Name {get;set;}
}
how do I select the properties B1Volume,........B24Volume ?
I tried with following code mentioned below, but it's not giving expected results
var hp = foo.Skip(1).Take(23).ToList();
There's a few ways, but I do not think that you want to go down that road.
Do you really want a list of xyz? Or asked in a different fashion: Do you have many different lists of lists of volumes? Or do you only want to express a single list of volumes?
Maybe what you want to do is declare an array inside XYZ like this
public class XYZ
{
public String TimeZone { get; set; }
public Decimal?[] Volumes {get; set;} = new Decimal?[24];
public String Name {get; set;}
}
If you want to access volumes by an index (1,2,...,24) you need an array or any other kind of indexed data structure.
Then you could do
var xyz = new XYZ();
xyz.Volumes[0] = 12.0;
xyz.Volumes[1] = 23.0;
.....
and basically access the volumes by xyz.Volumes and adding an index to get the n-th volume
If you now want to further list these XYZ you could do something like this:
var listOfXyz = new List<XYZ>();
listOfXyz.Add(new XYZ());
....
listOfXyz[3].Volumes
this would give you the 24 volumes of the element at the index of 3 in the list.
You need to do a Select:
var hp = foo.Select(x => new { x.BVolume1, x.BVolume2, ..., x.BVolume24 });
Although I do agree with #Himzo that this is not the best way to solve your problem if you can change the structure.
Maybe it helps:
XYZ xyz = new XYZ();
Type t = xyz.GetType();
List<PropertyInfo> properties = new List<PropertyInfo>(t.GetProperties());
var hp = properties.Skip(1).Take(23).ToList();
Do not forget adding name space:
using System.Reflection;
Update
In comments GBreen12 suggests to add a filter for getting only properties that has name containing volume. Now if you add another properties the code will not fail. So you can change the 3th line to this:
List<PropertyInfo> properties = (new List<PropertyInfo>(t.GetProperties())).Where(x => x.Name.EndsWith("Volume")).ToList();
Now you do not need last line var hp = ... and the properties is your answer.

Serialize an element value with its attributes

I have an XML coming in this form:
<run>
<foo status="1">111.9</foo>
<fred status="0">5.5</fred>
</run>
I would like to deserialize this in either of these forms below (I'm undecided, and hoping an answer will help me decide, although I tend to prefer #1, for design aesthetics as much as anything else):
Case # 1
[Serializable]
public class DataValue
{
[XmlAttribute("status")]
public int Status { get; set; }
// I need something here, but what?
public float Value { get; set; }
}
[Serializable]
[XmlRoot("run")]
public class DataBag
{
[XmlElement("foo")]
public DataValue Foo{ get; set; }
[XmlElement("fred")]
public DataValue Fred{ get; set; }
}
When I try this, I get a value of 0 for either member foo or fred.
Case # 2
[Serializable]
[XmlRoot("run")]
public class DataBag2
{
[XmlElement("foo")]
public float Foo{ get; set; }
[XmlElement("foo")]
[XmlAttribute("status")]
public int Foo_status { get; set; }
[XmlElement("fred")]
public float Fred{ get; set; }
[XmlElement("fred")]
[XmlAttribute("status")]
public int Fred_status { get; set; }
}
It compiles but I get an InvalidOperationException while reflecting Foo_status, for which the innermost exception is "For non-array types, you may use the following attributes: XmlAttribute, XmlText, XmlElement, or XmlAnyElement."
What can I do to end up with an actual value in case #1, or no exception (and a valid value and status) for case #2?
The code for the serialization goes like this:
// Case 1
using (var sr = new StreamReader("data.xml"))
{
var xs = new XmlSerializer(typeof(DataBag));
var run = (DataBag)xs.Deserialize(sr);
Console.WriteLine("Got a run: {0}-{1}", run.Fred.Value, run.Fred.Status);
// Issue here is that value is always 0, but status is accurate
}
// case 2
using (var sr = new StreamReader("data.xml"))
{
var xs = new XmlSerializer(typeof(DataBag2));// Exception here
var run = (DataBag2)xs.Deserialize(sr);
Console.WriteLine("Got a run: {0}-{1}", run.Foo, run.Foo_status);
}
Thanks for your attention!
For case 1 you just need to mark it as XMLText:
[XmlText]
public float Value { get; set; }
You want to use [XmlText]:
Indicates to the XmlSerializer that the member must be treated as XML text when the class that contains it is serialized or deserialized.
Thus:
public class DataValue
{
[XmlAttribute("status")]
public int Status { get; set; }
[XmlText]
public float Value { get; set; }
}
Case #2 just won't work as you want. Adding [XmlAttribute("status")] to Foo_status means that Foo_status will be serialized as an attribute of DataBag2, not Foo. Applying [XmlElement("foo")] as well then says it's an element of DataBag2, which is of course in conflict with other attribute.
There's no way with XmlSerializer for an outer container type to specify an attribute to be applied to a nested element.

How to get total Sum of my Collection/Class

I have this class:
public class ExtraDisplayItems
{
public int ItemId { get; set; }
public string ItemCode { get; set; }
public string ItemDescription { get; set; }
public double? ItemSellingPrice { get; set; }
}
I then add data to the class with the method below:
using (TruckServiceClient TSC = new TruckServiceClient())
{
var item = cmbAddExtras.SelectedItem as ExtraDisplayItems;
if (item != null)
{
var displayItem = new List<ExtraDisplayItems>
{
new ExtraDisplayItems
{
ItemId = item.ItemId,
ItemCode = item.ItemCode,
ItemDescription = item.ItemDescription,
ItemSellingPrice = item.ItemSellingPrice
}
};
dgAddExtras.Items.Add(item);
}
}
Now what I want to do is create a read-only property where I would get the total Sum of the ItemSellingPrice, but in this property I cannot reach the Sum when trying to calculate the double?
This is how I wan't the coding to work:
public double? SubTotalExtras
{
get { return ExtraDisplayItems.Sum(x => x.ItemSellingPrice); }
}
But... it's giving me the error:
ExtraDisplayItems does not contain a definition for 'Sum'
How would I go about fixing this?
EDIT
I have changed the class to:
public class ExtraDisplayItems
{
private List<ExtraDisplayItems> displayItems;
public int ItemId { get; set; }
...
}
And...
if (item != null)
{
this.displayItems = new List<ExtraDisplayItems> //Error
{
new ExtraDisplayItems
{
...
}
};
dgAddExtras.Items.Add(item);
}
But it now throws the error: does not contain definition for 'displayItems'
It seems dat ExtraDisplayItems is an instance of the ExtraDisplayItems class, which doesn't implement IEnumerable<T> (that is: 'it is a collection of something'). That means that it indeed doesn't have a Sum method, nor does the extension method apply.
My best guess at the moment is that you should save the displayItem (which is a List<ExtraDisplayItems>, so it implements IEnumerable<ExtraDisplayItems>) somewhere in your view model and call Sum on that.
Also, use naming conventions: you are making plurals singular and the other way around... Very confusing...
You are probably missing Linq in your using definitions.
using System.Linq;
EDIT:
I was assuming ExtraDisplayItems was a collection, then it is common to have the error 'x does not contain a definition for 'Sum'' even if you see it in code samples.
In order to do the sum various objects you actually need a collection of them not a single object.

Dapper.NET multi mapping TSecond Deserializer is null

I'm trying to perform a very standard multi mapping query using Dapper, and I'm getting the following error. I also get another error occasionally when this seems to work, but I'm unable to reproduce it at the moment. I'll append it to this post if/when the first problem is solved.
Here is the query code:
const string storedProc = "dbo.GetStopsForRouteID";
var stops = conn.Query<RouteStop, MapLocation, RouteStop>(
storedProc, (stop, loc) =>
{
stop.Location = loc;
return stop;
}, new { RouteID = routeId }, commandType: CommandType.StoredProcedure);
In Dapper.cs on line 498:
var deserializer2 = (Func<IDataReader, TSecond>)info.OtherDeserializers[0];
info.OtherDeserializers is null which causes a NullReferenceException.
This is the guts of the stored procedure:
SELECT
RouteStops.StopID,
RouteStops.Name,
RouteStops.Description,
RouteStops.IsInbound,
RouteStops.Location.Lat as Latitude,
RouteStops.Location.Long as Longitude
FROM dbo.Routes
INNER JOIN dbo.StopsOnRoute ON
Routes.RouteID = StopsOnRoute.RouteID
INNER JOIN dbo.RouteStops ON
StopsOnRoute.StopID = RouteStops.StopID
WHERE Routes.RouteID = #RouteID
ORDER BY StopsOnRoute.SequenceNumber
I've had an extensive look at the dapper code but I can't find anything that seems out of place other than that TFirst's deserialiser isn't null, but TSecond's is. Could there be a problem when it creates TSecond's deserializer that leaves it as null?
Here are the types:
public class MapLocation
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class RouteStop {
public int StopID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsInbound { get; set; }
public MapLocation Location { get; set; }
}
Probably the main problem here is that you haven't told it how to "split"; try adding the parameter:
splitOn: "Latitude"
without that, as far as dapper can see there is no second result portion (it splits on Id by default).

Categories