I'm trying to set up a Test Harness and I have some data I want to include in my project in XML format and load that into the business objects in the test pre-setup method.
The class structure is
public class DbUserChoice
{
public int Id { get; set; }
public string Description { get; set; }
}
public class DbUserAmbition : DbUserChoice { }
public class DbUserDiet : DbUserChoice { }
public class DbUserEthnicity : DbUserChoice { }
etc...
So there is an abstract base class DbUserChoice that is then extended by all the different types of choices a user has (15 in total). All of these except one add nothing to the class, just extending it verbatim.
The XML structure is (partial)
<UserChoiceOptions>
<UserChoice ChoiceType="DbUserAmbition">
<Choice>I'm content to just sit back and enjoy life</Choice>
<Choice>I have a few ambitions and dreams but keep my feet on the ground</Choice>
<Choice>I'm quite ambitious and driven in my career and personal life</Choice>
<Choice>I'm extremely driven to succeed and want the very best from life</Choice>
</UserChoice>
<UserChoice ChoiceType="DbUserBodyType">
<Choice Gender="M">Slim</Choice>
<Choice Gender="M">Athletic and toned</Choice>
<Choice Gender="M">A healthy medium</Choice>
<Choice Gender="M">Muscular</Choice> etc...
I want some kind of generic method that I can pass a DataType to, and it returns me an IQueryable of the choices that map to that datatype in the XML, chosen by the "ChoiceType" discriminator attribute on the UserChoice node above.
Eg
var ambitions = TestUtil.ReadXMLObjects<DbUserAmbition>(xmlFilePath);
would return me an IQueryable<DbUserAmbition> with the above 4 options
14 of the 15 user choice types would behave in this way. The only one different is the DbUserBodyType one also mentioned above, as you can see this has an additional attribute on each record of Gender. This is a new property on the DbUserBodyType class, it is the only derived class that adds a new record to the base class, and this woudl also need to be popualted from that XML attribute.
I've been trying to achieve this using Linq to XML but I can't seem to quite get it right. The following code gives me a list of XElements but I can't see how to easily and cleanly translate that to a list of C# DbUserAmbition objects for example without using messy reflection.
var element = XElement.Load(_xmlPath);
var typeName = typeof(T).Name;
var nodes = from n in element.Elements("UserChoiceOptions/UserChoice/Choice")
where n.Parent.Attribute("ChoiceType").Value.Equals(typeName, StringComparison.CurrentCultureIgnoreCase)
select n;
Any advice would be welcome
Well, you can achive this by declaring a method which would parse data from xml:
public class DbUserChoice
{
public int Id { get; set; }
public string Description { get; set; }
public virtual void Parse(XElement node)
{
//assign properties from XElement here
//override this method in DbUserBodyType to add additional logic
}
}
Then your query will end with:
return nodes.Select(n => { var x = new T(); x.Parse(n); return x; }).AsQueryable();
This will add a restriction T : DbUserChoice, new() to your generic method, but that should not be a problem
Related
I want to create a method that displays the information contained in an object, that will work dynamically, with any object. I'm having trouble handling properties that are other custom classes. In the example below the Person has Phones and Occupations which both are other classes. When the data is displayed, the value on the screen currently is:
TestReflection.Person
Name: Mary
Phones: TestReflection.Phones
Occupations: TestReflection.Occupations
It just displays the name of class, like TestReflection.Phones, rather than the data inside that object.
How can I change this code to show information like this instead?
TestReflection.Person
Name: Mary
Phones:
TestReflection.Phones
Type: 1
Number: 555XYZ
Occupations:
TestReflection.Occupations
Type: 5
Description: Secretary
Here is my code:
class Program
{
static void Main(string[] args)
{
List<Person> listPeson = new List<Person>();
var person1 = new Person();
person1.Name = "Mary";
person1.Phones = new Phones { new Phone { Type = 1, Number = "555XYZ" } };
person1.Occupations = new Occupations {new Occupation { Type = 5, Description = "Secretary" }};
listPeson.Add(person1);
DynamicExport(listPeson);
Console.ReadLine();
}
public static void DynamicExport<T>(List<T> listReg)
{
for (int i = 0; i < listReg.Count; i++)
{
Console.WriteLine(listReg[i].GetType());
foreach (var item in listReg[i].GetType().GetProperties())
{
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
}
}
}
}
class Person
{
public string Name { get; set; }
public Phones Phones { get; set; }
public Occupations Occupations { get; set; }
}
class Phones : List<Phone> { }
class Phone
{
public int Type { get; set; }
public string Number { get; set; }
}
class Occupations : List<Occupation> { }
class Occupation
{
public int Type { get; set; }
public string Description { get; set; }
}
I made some edits to your question - I hope I understood you correctly.
If you want to export data
If your question is really about displaying data, then there are better ways to do it than creating your own export method. The format you are trying to display looks similar to YAML. There's also JSON and XML. Using one of these libraries is probably better than writing your own method:
YamlDotNet NuGet package
Json.NET NuGet Package
System.Xml.Serialization.XmlSerializer class
If you want to learn more about reflection
Maybe you're interested in learning more about reflection, and the export is just an example to play around with it. In that case, let's look at this line:
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
$"{item.GetValue(listReg[i], null)}" ends up calling person1.Phones.ToString(). The default behavior of ToString just displays the type name. You could override that behavior, like this:
class Phones : List<Phone>
{
public override string ToString()
{
return Program.DynamicExportToString(this);
// ... where DynamicExportToString is a modified version of DynamicExport that
// builds and returns a string rather than sending it directly to the Console.
}
}
Maybe you want to be able to handle any class, even when you cannot override ToString in all of the classes you might export. Then you will need to put some additional logic in the DynamicExport method, because...
$"{item.Name}: {item.GetValue(listReg[i], null)}"
... doesn't work for every situation. We need to display different things depending on the type of the property.
Consider how you want to handle null values. Maybe something like $"{item.Name}: <null>"
Use your existing $"..." code if the type is...
a primitive type.
DateTime
String
... or a Nullable<> of one of those types.
If the type implements IEnumerable, loop over the contents of the collection and recursively call your export code for each element.
It's important to check for this interface after you've checked if the type is a String, because String implements IEnumerable.
Otherwise, recursively call your export code on this value.
When you call your export code recursively, it would be wise to guard against infinite loops. If the object you're trying to export contains a circular reference - you could quickly wind up with a StackOverflowException. To avoid this, maintain a stack of objects that have already been visited.
I think the above advice is generally applicable whenever you're using reflection to traverse an object graph - whether it's for serialization or any other purpose.
I hope this helps!
I've got a large number of tables in my database that are essentially supporting data. These tables list nationalities, genders, languages, etc. and are all based on the same data model:
public class SupportDataModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Deleted { get; set; }
}
public class Gender : SupportDataModel
{
}
This data is presented in DropDownList controls mostly so I need to query each table to get a list. Since I don't want to have to rewrite this query every time I need to access the data, I've written it as a helper class:
public class GendersHelper : IAlternateHelper<Gender>
{
public List<Gender> ListItems()
{
using (var db = new ApplicationDbContext())
{
return db.Genders.Where(x => !x.Deleted).ToList();
}
}
}
For each of these classes, this function is identical except in the table it queries. That's why I'd like to write a single class that uses the type that I pass in to it as the determining factor for which table I'm querying, but I don't know how to do this.
Here's what I've got so far...
public abstract class SupportingDataHelper<T>
{
public List<T> ListItems()
{
// Logic to determine which table gets queried,
// as well as the query itself should go here.
}
}
How do I get this method to determine from the type passed in which table to query and then return a list of those items?
You can just use DbContext.Set<T> which returns a set for selected type:
public class SupportDataRepository<T> where T : SupportDataModel
{
public List<T> ListItems()
{
using (var db = new ApplicationDbContext())
{
return db.Set<T>().Where(x => !x.Deleted).ToList();
}
}
}
However, I wouldn't call this class Helper, because it looks more like a repository.
Another thing to consider is that you definitely don't want to create an empty class like:
public class Gender : SupportDataModel
{
}
because it doesn't make much sense. Perhaps, you may want to use enum property to define a type of SupportDataModel. In this case, you will have only one table (with more rows though), one simple class with simple repository class and no inheritance or generics.
How can I project into class property using NHibernate? For example:
[Test]
public void Test()
{
MyClass dto = null;
var test = CurrentSession.CreateCriteria<Contact>()
.Add(Restrictions.Eq("ContactName", "John Smith"))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("ContactName").WithAlias(() => dto.SubClass.Name))
.Add(Projections.Property("EmailAddress").WithAlias(() => dto.Email))
)
.SetResultTransformer(Transformers.AliasToBean<MyClass>())
.List<MyClass>();
Assert.That(test[0].SubClass.Name, Is.EqualTo("John Smith"));
}
class MyClass
{
public string Email { get; set; }
public MySubClass SubClass { get; set; }
}
class MySubClass
{
public string Name { get; set; }
}
As you see I have a simple query and want to transform 1 row into 1 object - without lists but with a subclass. Unfortunately, it fails:
NHibernate.PropertyNotFoundException : Could not find a setter for property 'Name' in class 'MyTest+MyClass'
Is it possible to achieve this behaviour without custom transformer?
The default result transformer will be able to fill the root entity properties. But we can introduce our custom result transformer. There is one I do use:
DeepTransformer<TEntity> : IResultTransformer
Which is ready to convert . notation into chain of inner objects (excluding collections)
So, if you'll take it, and reuse it, this syntax would work:
...
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("ContactName").As("SubClass.Name"))
.Add(Projections.Property("EmailAddress").As("Email"))
)
.SetResultTransformer(DeepTransformer<MyClass>())
You can even improve it, but the idea of custom transformer should be clear now
I am retrieving and successfully deserializing an xml string from a database table column for the known element names (these do not change) but there are also some nested XML Elements called "Other Attributes" which are not always going to be known. I'm having some trouble deserialising these unknown element names to a generic list so I can display them in html once deserialised.
The XML is as follows:
<Detail>
<DetailAttributes>
<Name>Name_123</Name>
<Type>Type_123</Type>
</DetailAttributes>
<OtherAttributes>
<SummaryKey AttributeName="SummaryKey">SummaryKey_123</SummaryKey>
<Account AttributeName="Account">Account_123</Account>
</OtherAttributes>
</Detail>
I have no problem deserialising the 'Name' and 'Type' elements and I can deserialise the 'SummaryKey' and 'Account' elements but only if I explicitly specify their element names - which is not the desired approach because the 'OtherAttributes' are subject to change.
My classes are as follows:
[XmlRoot("Detail")]
public class objectDetailsList
{
[XmlElement("DetailAttributes"), Type = typeof(DetailAttribute))]
public DetailAttribute[] detailAttributes { get; set; }
[XmlElement("OtherAttributes")]
public List<OtherAttribute> otherAttributes { get; set; }
public objectDetailsList()
{
}
}
[Serializable]
public class Detail Attribute
{
[XmlElement("Type")]
public string Type { get;set; }
[XmlElement("Name")]
public string Name { get;set; }
public DetailAttribute()
{
}
}
[Serializable]
public class OtherAttribute
{
//The following will deserialise ok
//[XmlElement("SummaryKey")]
//public string sumKey { get; set; }
//[XmlElement("Account")]
//public string acc { get; set; }
//What I want to do below is create a list of all 'other attributes' without known names
[XmlArray("OtherAttributes")]
public List<Element> element { get; set; }
}
[XmlRoot("OtherAttributes")]
public class Element
{
[XmlAttribute("AttributeName")]
public string aName { get; set; }
[XmlText]
public string aValue { get; set; }
}
When I try to retrieve the deserialised list of OtherAttribute elements the count is zero so it's not able to access the elements nested within "Other Attributes".
Can anybody help me with this please?
With concrete classes and dynamic data like this, you won't be able to lean on the standard XmlSerializer to serialize / deserialize for you - as it reflects on your classes, and the properties you want to populate simply don't exist. You could provide a class with all possible properties if your set of 'OtherAttributes' is known and finite, and not subject to future change, but that would give you an ugly bloated class (and I think you've already decided this is not the solution).
Practical options therefore:
Do it manually. Use the XmlDocument class, load your data with .Load(), and iterate the nodes using .SelectNodes() using an XPath query (something like "/Detail/OtherAttributes/*"). You will have to write the lot yourself, but this gives you complete control over the serialization / deserialization. You won't have to cover your code in (arguably superfluous!) attributes either.
Use Json.NET (http://james.newtonking.com/json), it allows for far greater control over serialization and deserialization. It's fast, has good docs and is overall pretty nifty really.
I have a mongo model like this:
class ObjectA {
[BsonId(IdGenerator = typeof(BsonObjectIdGenerator))]
public BsonObjectId Id;
[BsonElement("number")]
public int Number { get; set; }
[BsonElement("b")]
public List<ObjectB> objectB { get; set; }
}
class ObjectB {
[BsonElement("someProperty")]
public string SomeProperty { get; set; }
}
My problem is when I aggregate the collection with {$unwind:objectB}. The result documencts have a unique object on the property objectB (not a list).
So the cast failes with the exception:
An error occurred while deserializing the ObjectB property of class
ObjectA: Expected element name to be '_t', not
'number'.
Do I have to create a new model for this or is there a easier way to solve it?
You could also choose to work with BsonDocument directly (but that is not strongly typed and more cumbersome to work with), e.g. (I'm using the simple Posts/Tags example here)
var aggregationResults = db.GetCollection("Posts").Aggregate().ResultDocuments;
foreach (var document in aggregationResults)
{
var tag = document.GetValue("Tags").AsString;
}
Unlike the normal query and projection operators, the aggregation framework may change the structure of your document. As you already pointed out, $unwind transforms a document that contains an array into a number of documents that each have a single value of the same name.
Another approach this is to indeed create a new type for this, so
class Post {
public List<string> Tags { get; set; }
...
would become
class PostAggregationResult {
public string Tags { get; set; }
...
That is very easy to work with, but if you have very various aggregation queries, you need a large number of classes which can be annoying.