Automapper to XML serialization producing unwanted parent XML elements - c#

I'm using automapper to map out part of my game to an XML file so that I can export it:
cfg.CreateMap()
.ForMember(d => d.RoomType, opt => opt.MapFrom(src => src.GetRoomType()))
For some reason that I cannot figure out, it is adding a parent element to the XML like this:
<RoomType>
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
</RoomType>
I don't want the XML structure to look like that, I just want the elements like this:
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
Here is the method GetRoomType:
public static IEnumerable<RoomType> GetRoomType(this DungeonRoom room)
{
var customType = room.Monsters
.Where(e => e.Treasure.Category.IsActive);
if (room.RoomType?.MonsterType == null) yield break;
if (room.RoomType.MonsterType.StartsWith("X_Y"))
{
yield return new RoomType(room.RoomType.MonsterType);
}
foreach (PageTreasure pe in customType)
{
if (customType == null) yield break;
if (RoomLookupByTypeId.ContainsKey(pe.Treasure.Id))
yield return RoomLookupByTypeId[pe.Treasure.Id];
}
}
}
And here is the RoomLookupByTypeId() method:
public static readonly IDictionary<Int32, RoomType> RoomLookupByTypeId =
new Dictionary<Int32, RoomType> {
{373, new RoomType("X_Y001")},
{488, new RoomType("X_Y002")},
{467, new RoomType("X_Y003")},
{238, new RoomType("X_Y004")},
{756, new RoomType("X_Y005")}
};
And here is the class that is being mapped out to XML:
public class RoomType
{
[XmlText]
public String TypeId { get; set; }
[XmlAttribute("monster")]
public String Monster
{
get { return "Random"; }
set { throw new NotSupportedException("Computed property, setter only exists for XmlSerializer"); }
}
public RoomType() { }
public RoomType(String Id)
{
TypeId = Id;
}
}
And this is an example result:
<RoomType>
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
</RoomType>
So the data is correct, but it's adding that opening and closing <RoomType> which it does not need.
I'm not sure why it's doing that.
At first, I thought it was because GetRoomType() was returning something other than 'null', but I added 'yield break' to stop it from returning anything if it finds a null.
So I'm at a loss. I have no idea why it's doing this.
Does anyone see anything that could be causing it?
Thanks!

Related

C# jagged array get property instantiate linq

I have a Model object that contains a list of node. These nodes contain a signature.
I would like to have a property with a getter returning an array of signatures. I have trouble to instantiate the array and I'm not sure if I should use an array/list/enumerable or something else.
How would you achieve this?
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
var m = new Model();
Console.WriteLine(m.Signatures.ToString());
Console.ReadLine();
}
}
public class Model
{
public List<Node> Nodes { get; set; }
public int[][] Signatures
{
get
{
return Nodes.Select(x => x.Signature) as int[][];
}
}
public Model()
{
Nodes = new List<Node>();
Nodes.Add(new Node { Signature = new[] { 1,1,0,0,0 } });
Nodes.Add(new Node { Signature = new[] { 1,1,0,0,1 } });
}
}
public class Node
{
public int[] Signature { get; set; }
}
}
Use ToArray()
return Nodes.Select(x => x.Signature).ToArray();
And something like this to output it correctly:
Array.ForEach(m.Signatures, x=>Console.WriteLine(string.Join(",", x)));
In your Signatures property you try to use the as operator to convert the type into int[][]. The Select method however returns an IEnumerable<int[]> which is not an array. Use ToArray to create the array:
public int[][] Signatures
{
get
{
return Nodes.Select(x => x.Signature).ToArray();
}
}

Populating and XML template with LINQ to XML?

I have multiple XML templates and I would like to populate them using LINQ to XML, but I wasn't sure how to get started, meaning how to correctly read in the XML file (Best method) and populate attributes and Nodes (InnerText). Also, how do create new elements and attributes in the existing template as well remove/update existing ones? Here is an example of a template:
<Person age="age">
<FirstName></FirstName>
<LastName></LastName>
<Children>
<Name></Name>
</Children>
</Person>
In the abvoe, the age attribute is a placeholder, FirstName, LastName and Name under Children are placeholders. Let's also assume that I either want to remove Names under children or add names as well. Also lets assume I want to add a DOB element inside Person with day, month, year attributes?
Here's an example. You build a class for each template. I'm using this library extension class (XElementExtensions.cs) from: https://github.com/ChuckSavage/XmlLib/
public class Person
{
XElement self;
public Person(XElement person) { self = person; }
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); } // true set as attribute
}
public string FirstName
{
get { return self.Get("FirstName", string.Empty); }
set { self.Set("FirstName", value, false); } // false set as child node
}
public string LastName
{
get { return self.Get("LastName", string.Empty); }
set { self.Set("LastName", value, false); }
}
public Children Children
{
get { return new Children(self.GetElement("Children")); }
}
}
public class Children
{
XElement self;
public Children(XElement children) { self = children; }
public Child this[string name]
{
get
{
return self.Elements("Name")
.Select(x => new Child(x))
.FirstOrDefault(c => c.Name == name);
}
}
public Child this[int index]
{
get { return new Child(self.Elements("Name").ElementAt(index)); }
}
public void Add(string name)
{
Child child = this[name];
if(null == child)
{
child = new Child(new XElement("Name"));
child.Name = name;
self.Add(child.self);
}
else
throw new ArgumentException("Child with name: "+ name +" already exists!");
}
}
public class Child
{
internal XElement self;
public Child(XElement child) { self = child; }
// You can have an Age/DOB for the child as well, or remove these two properties.
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
// DOB is an attribute as self.Value in Name erases all child nodes when set.
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); }
}
public string Name
{
get { return self.Value }
set { self.Value = value; }
}
}
If you want to specify null as the default for any Get() then you need to specify the type to Get with Get<Type>("node", null)
I would suggest to take a look at the LINQ to XML and its XDocument class. It makes navigation and editing of an xml document very easy.
Look at this site for samples of linq to xml

C# Serialize a class with a List data member

I have this c# class:
public class Test
{
public Test() { }
public IList<int> list = new List<int>();
}
Then I have this code:
Test t = new Test();
t.list.Add(1);
t.list.Add(2);
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
StringWriter sw = new StringWriter();
XmlSerializer xml = new XmlSerializer(t.GetType());
xml.Serialize(sw, t);
When I look at the output from sw, its this:
<?xml version="1.0" encoding="utf-16"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
the values 1,2 I added to the list member variable dont show up.
So how can I fix this ? I made the list a property but it still doesnt seem to work.
I am using xml serialization here, are there any other serializers ?
I want performance! Is this the best approach ?
--------------- UPDATE BELOW -------------------------
So the actual class I want to serialize is this:
public class RoutingResult
{
public float lengthInMeters { get; set; }
public float durationInSeconds { get; set; }
public string Name { get; set; }
public double travelTime
{
get
{
TimeSpan timeSpan = TimeSpan.FromSeconds(durationInSeconds);
return timeSpan.TotalMinutes;
}
}
public float totalWalkingDistance
{
get
{
float totalWalkingLengthInMeters = 0;
foreach (RoutingLeg leg in Legs)
{
if (leg.type == RoutingLeg.TransportType.Walk)
{
totalWalkingLengthInMeters += leg.lengthInMeters;
}
}
return (float)(totalWalkingLengthInMeters / 1000);
}
}
public IList<RoutingLeg> Legs { get; set; } // this is a property! isnit it?
public IList<int> test{get;set;} // test ...
public RoutingResult()
{
Legs = new List<RoutingLeg>();
test = new List<int>(); //test
test.Add(1);
test.Add(2);
Name = new Random().Next().ToString(); // for test
}
}
But the XML produced by the serializer is this:
<RoutingResult>
<lengthInMeters>9800.118</lengthInMeters>
<durationInSeconds>1440</durationInSeconds>
<Name>630104750</Name>
</RoutingResult>
???
its ignoring both of those lists ?
1) Your list is a field, not a property, and the XmlSerializer will only work with properties, try this:
public class Test
{
public Test() { IntList = new List<int>() }
public IList<int> IntList { get; set; }
}
2) There are other Serialiation options, Binary the main other one, though there is one for JSON as well.
3) Binary is probably the most performant way, since it is typically a straight memory dump, and the output file will be the smallest.
list is not a Property. Change it to a publicly visible property, and it should be picked up.
I figured it out that XmlSerializer doesnt work if I use IList so I changed it to List, that made it work. As Nate also mentioned.

Variable initalisation in while loop

I have a function that reads a file in chunks.
public static DataObject ReadNextFile(){ ...}
And dataobject looks like this:
public DataObject
{
public string Category { get; set; }
// And other members ...
}
What I want to do is the following basically
List<DataObject> dataObjects = new List<DataObject>();
while(ReadNextFile().Category == "category")
{
dataObjects.Add(^^^^^ the thingy in the while);
}
I know it's probably not how it's done, because how do I access the object I've just read.
I think what you're looking for is:
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "category")
{
dataObjects.Add(nextObject);
}
But I wouldn't do that. I'd write:
List<DataObject> dataObject = source.ReadItems()
.TakeWhile(x => x.Category == "Category")
.ToList();
where ReadItems() was a method returning an IEnumerable<DataObject>, reading and yielding one item at a time. You may well want to implement it with an iterator block (yield return etc).
This is assuming you really want to stop reading as soon as you find the first object which has a different category. If you actually want to include all the matching DataObjects,
change TakeWhile to Where in the above LINQ query.
(EDIT: Saeed has since deleted his objections to the answer, but I guess I might as well leave the example up...)
EDIT: Proof that this will work, as Saeed doesn't seem to believe me:
using System;
using System.Collections.Generic;
public class DataObject
{
public string Category { get; set; }
public int Id { get; set; }
}
class Test
{
static int count = 0;
static DataObject ReadNextFile()
{
count++;
return new DataObject
{
Category = count <= 5 ? "yes" : "no",
Id = count
};
}
static void Main()
{
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "yes")
{
dataObjects.Add(nextObject);
}
foreach (DataObject x in dataObjects)
{
Console.WriteLine("{0}: {1}", x.Id, x.Category);
}
}
}
Output:
1: yes
2: yes
3: yes
4: yes
5: yes
In other words, the list has retained references to the 5 distinct objects which have been returned from ReadNextFile.
This is subjective, but I hate this pattern (and I fully recognize that I am in the very small minority here). Here is how I do it when I need something like this.
var dataObjects = new List<DataObject>();
while(true) {
DataObject obj = ReadNextFile();
if(obj.Category != "category") {
break;
}
dataObjects.Add(obj);
}
But these days, it is better to say
List<DataObject> dataObjects = GetItemsFromFile(path)
.TakeWhile(x => x.Category == "category")
.ToList();
Here, of course, GetItemsFromFile reads the items from the file pointed to by path and returns an IEnumerable<DataObject>.
List<DataObject> dataObjects = new List<DataObject>();
string category = "";
while((category=ReadNextFile().Category) == "category")
{
dataObjects.Add(new DataObject{Category = category});
}
And if you have more complicated object you can do this (like jon):
List<DataObject> dataObjects = new List<DataObject>();
var category = new DataObject();
while((category=ReadNextFile()).Category == "category")
{
dataObjects.Add(category);
}
You should look into implementing IEnumerator on the class container the call to ReadNextFile(). Then you would always have reference to the current object with IEnumerator.Current, and MoveNext() will return the bool you are looking for to check for advancement. Something like this:
public class ObjectReader : IEnumerator<DataObject>
{
public bool MoveNext()
{
// try to read next file, return false if you can't
// if you can, set the Current to the returned DataObject
}
public DataObject Current
{
get;
private set;
}
}

question about linq select and ToList()

I have a problem with returning a list by executing a Select LINQ query. This is the query:
var data = Repository<EducationString>
.Find()
.ToList()
.Select(p => new EducationStringModel() {
Id = p.Id,
Title = p.Title,
EducationDegree=p.EducationDegree })
.ToList();
As you can see I used ToList() 2 times. I don't know why but when I delete the first ToList() I see this error, "Index was outside the bounds of the array", but by having both ToList() there is no problem.
Would it help if I said EducationDegree in EducationStringModel is an IList<EducationDegree>?
Is there anybody who knows the reason?
#Mark :its L2O
if u need to see the classes:
public class EducationStringModel
{
private IList _educationDegree = new List();
public IList EducationDegree
{
get
{
if (_educationDegree == null)
{
_educationDegree = new List();
}
return _educationDegree;
}
set { _educationDegree = value; }
}
public int? Id { get; set; }
public string Title { get; set; }
}
public class EducationString{
private string _title;
private IList _educationExperiences;
private IList _educationDegree;
virtual public string Title
{
get { return _title; }
set { _title = value; }
}
virtual public IList<EducationExperience> EducationExperiences
{
get
{
if (_educationExperiences == null)
{
_educationExperiences = new List<EducationExperience>();
}
return _educationExperiences;
}
set
{
_educationExperiences = value;
}
}
virtual public IList<EducationDegree> EducationDegree
{
get
{
if (_educationDegree == null)
{
_educationDegree = new List<EducationDegree>();
}
return _educationDegree;
}
set
{
_educationDegree = value;
}
}
}
Is that the actual code? The only unclear thing there is: what does Find() return?
It sounds like the ToList is helping here by breaking composition and using LINQ-to-Objects, in which case AsEnumerable() should work just as well. After that you just do a Select (which for L2O just takes each item in turn and applies the map). If Find() is something more exotic, it sounds like a bug in that LINQ provider (or perhaps more fairly: that provider struggling to cope with an atypical construct). Hard to say more without a fully reproducible example.

Categories

Resources