I'd like to map an arbitrary list of abstract types to an arbitrary set of properties, that share the same base type.
Here is some UnitTest code, which currently fails and which I want to success. Could you help me, get a generic solution?
Here are the classes:
public class Source
{
public string Name { get; set; } = "SomeName";
public Dictionary<string, ValueType> SourceList { get; set; } = new Dictionary<string, ValueType>();
}
public interface IDestination
{
string Name { get; set; }
}
public class Destination : IDestination //And many other classes like this, with other properties inherited from ValueType
{
public string Name { get; set; }
public double DoubleValue { get; set; }
public int IntValue { get; set; }
public string SomeOtherProperty { get; set; }
}
And here is the unit test, I'd like to succeed:
[TestMethod]
public void TestMethod1()
{
var source = new Source();
source.SourceList.Add("IntValue", (int) 3);
source.SourceList.Add("DoubleValue", (double) 3.14);
Mapper.Initialize(config =>
{
//Put in some magic code here!!!
});
var destinationAbstract = Mapper.Map<Source, IDestination>(source); //the type of destination is known only at runtime. Therefore Mapping to Interface
var destination = (Destination) destinationAbstract;
Assert.AreEqual(source.Name, destination.Name);
Assert.AreEqual((int)source.SourceList["IntValue"], destination.IntValue);
Assert.AreEqual((double)source.SourceList["DoubleValue"], destination.DoubleValue);
}
Please be aware, that
the number of classes, that inherit from IDestination is only known at runtime
the content of the SourceList may be different for each Source-instance and therefore the properties of the destination class could also change for each class definition
I hope you can help me, because I wasn't able to determine a generic solution with the help of the documentation.
Thanks in advance.
You can map from Dictionary<string, object>(property names to property values) to some class by default, without any extra configuration. The docs and the tests.
After considering Lucians hint and after trying different things with Automapper, I finally found a solution for my initial unit-test:
[TestMethod]
public void TestMethod1()
{
var source = new Source();
source.SourceList.Add("IntValue", (int) 3);
source.SourceList.Add("DoubleValue", (double) 3.14);
Mapper.Initialize(config =>
{
//"Magic code"
config.CreateMap<Source, IDestination>();
config.CreateMap(typeof(Source), typeof(Destination)).IncludeBase(typeof(Source), typeof(IDestination));
});
//standard map-call
var destination = Mapper.Map<Destination>(source);
//Additional "Trick":
Dictionary<string, object> mappingDict =
source.SourceList.ToDictionary(pair => pair.Key, pair => (object) pair.Value);
Mapper.Map(mappingDict, destination, typeof(Dictionary<string, object>), typeof(Destination));
Assert.AreEqual(source.Name, destination.Name);
Assert.AreEqual(source.SourceList["IntValue"], destination.IntValue);
Assert.AreEqual(source.SourceList["DoubleValue"], destination.DoubleValue);
}
The "trick" is to cast my Dictionary<string, ValueType> to Dictionary<string,object> and to map this dictionary-member to the destination object in addition(!) to the standard map-call.
This works, but has some drawbacks:
Mapping validation is not possible (Validation says: either source member "SourceList" is not mapped or the destination members "DoubleValue" or "IntValue" are not mapped)
Casting the dictionary is kind of ugly (and seems unnecessary to me...)
I need 2 calls to Mapper.Map instead of only one.
It seems to me, that there is no other way to solve my initial problem. But I am open for any suggestions or improvements to my solution.
The initial problem could be solved also easily through using reflections, so all information for a proper mapping setup should be existent, but I was not able to find this proper mapping setup.
Related
Say I've a class like this one:
public class A
{
public IDictionary<string, object> Arguments { get; } = new Dictionary<string, object>
{
["entity"] = "teams"
}
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
And I want to map it to B, C, D types based on Arguments["entity"]:
config.CreateMap<A, B>();
config.CreateMap<A, C>();
config.CreateMap<A, D>();
Is there any way I could accomplish this using AutoMapper?
We need a more realistic use case. But if you feel this is close enough, the solution is to create the destination object yourself and then map into an existing object. Usually things like this are handled with Include. But you need a source and destination hierarchy for that. See also this.
How would I deserialize YAML to a immutable data structure?
e.g. I have this YAML:
Value: SomeString
Number: 99
And this data structure:
public class MyData
{
public MyData(string value, int number)
{
Value = value;
Number = number;
}
public string Value { get; }
public int Number { get; }
}
For this I'd to use the constructor. So somehow I'd need to first retrieve a Dictionary<string, object> parsed from the YAML respecting my class (so 99 would be int, not string), then scan my type for an appropriate constructor,
Although the question doesn't mention it, I'm assuming you are using YamlDotNet (or SharpYaml which is a fork of YamlDotNet)
YamlDotNet doesnt support deserializing into classes that do not have a default constructor - but one option to achieve what you want is to deserialize into an intermediate Builder type that is mutable which can produce the final type.
e.g.
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyData Build() => new MyData(Value, Number);
}
And then use something like:
deserializer.Deserialize<MyDataBuilder>(yaml).Build();
You would end up having to create a parallel set of builders for your whole model however, e.g. if MyData had a third parameter of type MyOtherData (I've changed the example to use records instead of classes to make it concise):
public record MyOtherData(string OtherValue);
public record MyData(string Value, int Number, MyOtherData otherData);
In which case we would need another Builder:
public class MyOtherDataBuilder
{
public string OtherValue { get; set; }
}
And MyDataBuilder would look like:
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyOtherDataBuilder MyOtherData { get; set; }
public MyData Build() => new MyData(Value, Number, MyOtherData.Build());
}
It's an old but surprisingly relevant question. Now, with records in C#, immutable collections in .net, lack of ability to deserialize immutable data is a blocker - there is no way we need to change all our data types just to be able to deserialize. One practical workaround that I found - is to convert yaml to json first, then deal with json your preferred way - System.Text.Json, Newtonsoft, etc.
Here is how to do is easiest way:
static string ConvertToJson(string yaml) {
object DeserializeYaml() =>
new DeserializerBuilder()
.Build()
.Deserialize(new StringReader(yaml))
?? throw new InvalidOperationException("Cannot deserialize yaml string:" + Environment.NewLine + yaml);
string SerializeYamlObjectToJson(object yamlObject) =>
new SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
return SerializeYamlObjectToJson(DeserializeYaml());
}
The only disadvantage, potentially big, is performance. I feel, however, that it's rarely an important requirement for yaml.
use the FormatterServices.GetUninitializedObject API (this will NOT invoke any constructors at all) and then use reflection to set fields.
Code example:
var instance = FormatterServices.GetUninitializedObject(typeof(MyData));
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var type = typeof(MyData);
var stringField = type.GetField("_value", flags);
stringField.SetValue(instance, "SomeString");
var numberField = type.GetField("_number", flags);
numberField.SetValue(instance, 99);
MyData data = (MyData)instance;
I have a large collection of automatically generated objects. Although they are all of different, non-related classes, all of the objects share some basic properties (name, id, etc.). I do not control the generation of these objects, so unfortunately I cannot take the ideal approach of implementing an interface. I would like to create a method in which I pass an arbitrary one of these objects and do something using these common properties.
The general idea would be something like:
someObj a = new someObj();
a.name = "sara";
diffObj b = new diffObj();
b.name = "joe";
string phrase = string.Format("I am with {0} and {1}",
getName(a), getName(b));
private string getName(object anyObjWithName)
{
return anyObjWithName.name;
}
though naturally this does not work.
I thought a generic method might hold the answer, but the only way I can see to call it with the current type is using genericMethod.Invoke , which still carries the same issue of not being able to resolve the properties of the passed object in the method. This is unlike Calling generic method with a type argument known only at execution time or How to call generic method with a given Type object? where only the type, or properties of the type, are used in the method, as opposed to properties of the object.
I am aware that this would be (very) prone to error, but I can guarantee that all objects encountered will have the common properties being manipulated.
I can guarantee that all objects encountered will have the common properties being manipulated
If that's the case, you can use dynamic:
private string getName(dynamic anyObjWithName)
{
return anyObjWithName.name;
}
Be aware that using any object that does not have a name property will not fail until run-time.
If you want to add a little bit of safety you can catch the RuntimeBinderException that gets thrown if the property does not exist:
private string getName(dynamic anyObjWithName)
{
try {
return anyObjWithName.name;
}
catch(RuntimeBinderException) {
return "{unknown}";
}
}
If you're unhappy with the performance using dynamic as mentioned by D Stanley, you could always try FastMember.
All you need to know to start using it is pretty much shown in the first 2 code examples.
You are creating a Rube Goldberg device there. You should just have all your data objects classes implement a single interface, then you can work on that. Much simpler and less error prone than fiddling with reflection.
The very fact that a lot of objects have common properties but don't share the same ancestry, on in the very least a common interface, shows that something is wrong with your design. Do rethink it.
Multiple ways to accomplish this, simplest probably is to create Interface and declare common methods there, have your object implement it, then change "getName" method take interface object
private string getName(IMyInterface anyObjWithName)
{
return anyObjWithName.name;
}
The correct way to do this is with an interface, if you own the types that you're working with
public interface IEntity
{
int ID { get; set; }
string Name { get; set; }
}
public class TypeOne : IEntity
{
public int ID { get; set; }
public string Name { get; set }
public string BespokePropertyOne { get; set;}
}
public class TypeTwo : IEntity
{
public int ID { get; set; }
public string Name { get; set; }
public float BespokePropertyTwo { get; set; }
}
static void Main(string[] args)
{
List<IEntity> entities = new List<IEntity>();
entities.Add(new TypeOne() { ID = 1, Name = "Bob", BespokePropertyOne = "blablabla" });
entities.Add(new TypeTwo() { ID = 2, Name = "Alice", BespokePropertyTwo = 5.4f });
foreach (IEntity entity in entities)
{
Console.WriteLine("ID: {0} Name: {1}", entity.ID, entity.Name);
}
}
This answer was written before the edit to the question stating that interfaces weren't possible in this case. Perhaps it can help someone else reading this question.
Interface:
interface Iname
{
string Name { get; set; }
}
Use interface:
class A : Iname
{
public string Name { get; set; }
}
class B : Iname
{
public string Name { get; set; }
}
The method:
string GetName(Iname o)
{
return o.Name;
}
Use:
A a = new A { Name = "First" };
B b = new B { Name = "Last" };
Text = GetName(a) + " " + GetName(b);
I'm trying to deserialize json to an object model where the collections are represented as IList<T> types.
The actual deserializing is here:
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize<IList<Contact>>(
(new StreamReader(General.GetEmbeddedFile("Contacts.json")).ReadToEnd()));
Before i post the exception i'm getting you should know what the implicit conversions are. This is the Contact type:
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public LazyList<ContactDetail> Details { get; set; }
//public List<ContactDetail> Details { get; set; }
}
And this is the ContactDetail type:
public class ContactDetail
{
public int ID { get; set; }
public int OrderIndex { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
The important thing to know with the LazyList<T> is that it implements IList<T>:
public class LazyList<T> : IList<T>
{
private IQueryable<T> _query = null;
private IList<T> _inner = null;
private int? _iqueryableCountCache = null;
public LazyList()
{
this._inner = new List<T>();
}
public LazyList(IList<T> inner)
{
this._inner = inner;
}
public LazyList(IQueryable<T> query)
{
if (query == null)
throw new ArgumentNullException();
this._query = query;
}
Now this LazyList<T> class definition was fine until i tried deserializing Json into it. The System.Web.Script.Serialization.JavaScriptSerializer seems to want to serialize lists to List<T> which makes sense coz of it's age but i need them in the type IList<T> so they will cast into my LazyList<T> (at least that's where i think i am going wrong).
I get this exception:
System.ArgumentException: Object of type 'System.Collections.Generic.List`1[ContactDetail]' cannot be converted to type 'LazyList`1[ContactDetail]'..
When i try using List<ContactDetail> in my Contact type (as you can see commented above) it seems to work. But i dont want to use List<T>'s. I even tried having my LazyList<T> inheriting from List<T> which seemed to execute but passing the List<T>'s internal T[] to my implementation was a nightmare and i simply don't want the bloat of List<T> anywhere in my model.
I also tried some other json libraries to no avail (it's possible i may not be using these to their full potential. I more or less replaced the references and attempted to repeat the code quoted at the top of this question. Maybe passing settings params will help??).
I dont know what to try now. Do i go with another deserializer? Do i tweak the deserializing itself? Do i need to change my types to please the deserializer? Do i need to worry more about implicit casting or just implement another interface?
It is not possible to deserialize directly to an interface, as interfaces are simply a contract. The JavaScriptSerializer has to deserialize to some concrete type that implements IList<T>, and the most logical choice is List<T>. You will have to convert the List to a LazyList, which given the code you posted, should be easy enough:
var list = serializer.Deserialize<IList<Contact>>(...);
var lazyList = new LazyList(list);
Unfortunately you will probably need to fix your class, as there is no way for a deserializer to know that it should be of type IList, since List is an implementation of IList.
Since the deserializers at http://json.org have source available you could just modify one to do what you want.
I ended up using the Json.NET lib which has good linq support for custom mapping. This is what my deserializing ended up looking like:
JArray json = JArray.Parse(
(new StreamReader(General.GetEmbeddedFile("Contacts.json")).ReadToEnd()));
IList<Contact> tempContacts = (from c in json
select new Contact
{
ID = (int)c["ID"],
Name = (string)c["Name"],
Details = new LazyList<ContactDetail>(
(
from cd in c["Details"]
select new ContactDetail
{
ID = (int)cd["ID"],
OrderIndex = (int)cd["OrderIndex"],
Name = (string)cd["Name"],
Value = (string)cd["Value"]
}
).AsQueryable()),
Updated = (DateTime)c["Updated"]
}).ToList<Contact>();
return tempContacts;
Question
Is there a way to define a method only once in C# (in a helper class or something) not knowing which type is given to be returned?
Long explanation
I get the following error:
Unable to cast object of type
System.Data.Objects.ObjectQuery1[WerkStageNu.Vacancies]'
to type
'System.Linq.IQueryable1[WerkStageNu.Models.IFilteredEntities]'.
I have a ListingsController which does a Search through my current Vacancies in the database:
public ActionResult Search(int? page, string branchid, string hoursago, string jobtypeid, string educationlevelid, string careerlevelid)
{
string searchResult = string.Empty;
const int pageSize = 10;
IQueryable<IFilteredEntities> selectedListings = (IQueryable<IFilteredEntities>)Repository.Instance._entities.Vacancies.AsQueryable();
Dictionary<string, string> filterParams = new Dictionary<string, string>() {
{"branchid", branchid}, {"hoursago", hoursago}, {"jobtypeid", jobtypeid}, {"educationlevelid", educationlevelid}, {"careerlevelid", careerlevelid}};
selectedListings = FilterByIDHelper.Filter(selectedListings, filterParams);
var paginatedDinners = new PaginatedList<Vacancies>(((IQueryable<Vacancies>)selectedListings).ToList(), page ?? 0, pageSize);
return View("Index", paginatedDinners);
}
Now, this search is just for Vacancies. But one can imagine we have searches all over the place all in general the same routine so I want to call the same method getting back different types. For this case I have made an Interface , IFilteredEntities. In my partial class Vacancies (partial class, class Vacancies is generated by my DB entity framework) I just do:
public partial class Vacancies : IFilteredEntities
And of course implement the methods in the Interface which are not implemented by Default. In my Interface I have:
interface IFilteredEntities
{
string EducationLevelID { get; set; }
string BrancheID { get; set; }
string CareerLevelID { get; set; }
string JobTypeID { get; set; }
Branches Branches { get; set; }
DateTime? DateOfCreation { get; set; }
CareerLevels CareerLevels { get; set; }
JobTypes JobTypes { get; set; }
EducationLevels EducationLevels { get; set; }
}
For convenience I have uploaded the two helper classes PaginatedList and FilterCriteriaHelper here and here.
Now, the method which would do the actual filtering is placed inside another helper class: FilterByIDHelper.cs.
public static IQueryable<IFilteredEntities> Filter(IQueryable<IFilteredEntities> collection, Dictionary<string, string> filterParams)
{
if (filterParams.ContainsKey("branchid")) collection = FilterByBranchId(collection, filterParams["branchid"]);
if (filterParams.ContainsKey("hoursago")) collection = FilterByHoursAgo(collection, filterParams["hoursago"]);
if (filterParams.ContainsKey("jobtypeid")) collection = FilterByJobTypeId(collection, filterParams["jobtypeid"]);
if (filterParams.ContainsKey("educationlevelid")) collection = FilterByEducationLevelId(collection, filterParams["educationlevelid"]);
if (filterParams.ContainsKey("careerlevelid")) collection = FilterByCareerLevelId(collection, filterParams["careerlevelid"]);
return collection;
}
public static IQueryable<IFilteredEntities> Filter(IQueryable<IFilteredEntities> collection, Dictionary<string, string> filterParams)
{
if (filterParams.ContainsKey("branchid")) collection = FilterByBranchId(collection, filterParams["branchid"]);
if (filterParams.ContainsKey("hoursago")) collection = FilterByHoursAgo(collection, filterParams["hoursago"]);
if (filterParams.ContainsKey("jobtypeid")) collection = FilterByJobTypeId(collection, filterParams["jobtypeid"]);
if (filterParams.ContainsKey("educationlevelid")) collection = FilterByEducationLevelId(collection, filterParams["educationlevelid"]);
if (filterParams.ContainsKey("careerlevelid")) collection = FilterByCareerLevelId(collection, filterParams["careerlevelid"]);
return collection;
}
For convenience here is a picture of a part of my solution explorer:
Solution Explorer http://www.bastijn.nl/zooi/solutionexplorer.png
In short:
What I try to do is instead of calling like:
selectedListings = Repository.Instance._entities.Vacancies.AsQueryable();
Dictionary<string, string> filterParams = new Dictionary<string, string>() {
{"branchid", branchid}, {"hoursago", hoursago}, {"jobtypeid", jobtypeid}, {"educationlevelid", educationlevelid}, {"careerlevelid", careerlevelid}};
selectedListings = FilterByIDHelper.Filter(selectedListings, filterParams);
var paginatedDinners = new PaginatedList<Vacancies>(selectedListings.ToList(), page ?? 0, pageSize);
return View("Index", paginatedDinners);
Call the variant shown up, using an Interface so I only have to define te "Filter" method once instead of for all classes / models. Now Notice that all of this DOES compile! The problem is that I get the following error:
Unable to cast object of type 'System.Data.Objects.ObjectQuery`1[WerkStageNu.Vacancies]' to type 'System.Linq.IQueryable`1[WerkStageNu.Models.IFilteredEntities]'.
I hope I have not forgotten any information but I'm already staring at this code for some while. Might forget a relation or something, just ask for it if I did :).
-----------------------------------------------------
EDIT AFTER COMMENTS
-----------------------------------------------------
O crap, nevermind this part, I forgot to as AsEnumerable, was still using AsQueryable.
It looks to me like this is a covariance vs. contravariance issue. Basically, an IQueryable<Vacancies> is not a sub-type of IQueryable<IFilteredEntities>, even though Vacancies implements IFilteredEntities. Thus, the line with the cast is causing a runtime error. So rather than doing the cast try this instead:
IEnumerable<IFilteredEntities> selectedListings =
Repository.Instance._entities.Vacancies.AsQueryable()
.OfType<IFilteredEntities>();
What this will do is project each element of the collection to an IFilteredEntities type.
Another option is to rewrite your filter methods so they use generics, like this:
public static IEnumerable<T> Filter<T>(
IEnumerable<T> collection, IDictionary<string, string> filterParams)
where T : IFilteredEntities
{
...
}
This would then allow you to pass in a collection containing any type that derives from IFilteredEntities and get back a collection of the same type. And if you're using C# 3, you don't even have to specify the type parameter if it can be implicitly determined by the compiler.