Is there an automagic (automapper?) way to map an entity to a runtime created dynamic object with properties passed as parameters? I want to do an API where the clients can select the properties of the entities they want to fetch.
I mean:
class Patient
{
public int PatientId{ get; set; }
public string Name{ get; set; }
public string LastName{ get; set; }
public string Address{ get; set; }
...
}
getPatient(string[] properties)
{
//Return an object that has the properties passed as parameters
}
Imagine you only want to fetch a PatientDTO with PatientId and Name:
getPatient(new string[]{"PatientId", "Name"}
should return
{
"PatientId": "1234",
"Name": "Martin",
}
and so on.
For now I'm doing it with Dictionary, but probably there is a better way. This is my approach:
For a single object:
public static Dictionary<string, object> getDTO(string[] properties, T entity)
{
var dto = new Dictionary<string, object>();
foreach (string s in properties)
{
dto.Add(s, typeof(T).GetProperty(s).GetValue(entity));
}
return dto;
}
For a list of objects:
public static List<Dictionary<string, object>> getDTOList(string[] properties, List<T> TList)
{
var dtoList = new List<Dictionary<string, object>>();
foreach(T entity in TList)
{
dtoList.Add(getDTO(properties, entity));
}
return dtoList;
}
Thank you.
How about creating a new dynamic object based solely on the specified property fields and returning the dynamic?
You will need to add using statements for: System.Dynamic and System.ComponentModel for the following to work.
public static dynamic getDTO(object entity, string[] properties)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var p in properties)
{
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(entity.GetType()))
{
if (property.Name == p)
{
expando.Add(p, property.GetValue(entity));
break;
}
}
}
return expando as ExpandoObject;
}
Calling this method would look something like this:
var patient = new Patient() { PatientId = 1, Name = "Joe", LastName = "Smith", Address = "123 Some Street\nIn Some Town, FL 32333" };
var propList = new string[] { "PatientId", "Name", "Address" };
dynamic result = getDTO(patient, propList);
Console.WriteLine("Id:{0} Name: {1}\nAddress: {2}", result.PatientId, result.Name, result.Address);
Console.ReadLine();
Related
This IDictionary<string, object> contains user data I'm logging into mongodb. The issue is the TValue is a complex object. The TKey is simply the class name.
For example:
public class UserData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Admin NewAdmin { get; set; }
}
public class Admin
{
public string UserName { get; set; }
public string Password { get; set; }
}
Currently, I'm trying to iterate through the Dictionary and compare types but to no avail. Is there a better way of doing this or am I missing the mark?
var argList = new List<object>();
foreach(KeyValuePair<string, object> kvp in context.ActionArguments)
{
dynamic v = kvp.Value;
//..compare types...
}
Just use OfType<>(). You don't even need the key.
public static void Main()
{
var d = new Dictionary<string,object>
{
{ "string", "Foo" },
{ "int", 123 },
{ "MyComplexType", new MyComplexType { Text = "Bar" } }
};
var s = d.Values.OfType<string>().Single();
var i = d.Values.OfType<int>().Single();
var o = d.Values.OfType<MyComplexType>().Single();
Console.WriteLine(s);
Console.WriteLine(i);
Console.WriteLine(o.Text);
}
Output:
Foo
123
Bar
Link to Fiddle
I have this serviceLayer Method that is used by my Web API proyect to return data to clients:
public IEnumerable<Contactos_view> ListarVistaNew(int activos, string filtro, int idSector, int idClient, string ordenar, int registroInic, int registros)
{
using (var myCon = new AdoNetContext(new AppConfigConnectionFactory(EmpresaId)))
{
using (var rep = base_getRep(myCon))
{
return rep.Listar(activos, filtro, idSector, idClient, ordenar, registroInic, registros);
}
}
}
Now the question is: How can I return only desired property of class Contactos_view? This class contains 20 properties, and my Idea is to add a parameter of type string[] Fields so client can select only the desired propeties.
Is it possible? what would be the returned type of ListarVistaNew in that case?
Thank you!
You can dynamically create and populate expando objects.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
namespace ClientSelectsProperties
{
public class OriginalType
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
class Program
{
// this simulates your original query result - it has all properties
private static List<OriginalType> queryResult = new List<OriginalType> {
new OriginalType { Id = 1, Name = "one", Description = "one description" },
new OriginalType { Id = 2, Name = "two", Description = "two description" }
};
// "hardcoded" property value readers, go crazy here and construct them dynamically if you want (reflection, code generation...)
private static Dictionary<string, Func<OriginalType, object>> propertyReaders = new Dictionary<string, Func<OriginalType, object>> {
{ "Id", t => t.Id },
{ "Name", t => t.Name },
{ "Description", t => t.Description }
};
static void Main(string[] args)
{
// your client only wants Id and Name
var result = GetWhatClientWants(new List<string> { "Id", "Name" });
}
private static List<dynamic> GetWhatClientWants(List<string> propertyNames)
{
// make sure your queryResult is in-memory collection here. Body of this select cannot be executed in the database
return queryResult.Select(t =>
{
var expando = new ExpandoObject();
var expandoDict = expando as IDictionary<string, object>;
foreach (var propertyName in propertyNames)
{
expandoDict.Add(propertyName, propertyReaders[propertyName](t));
}
return (dynamic)expando;
}).ToList();
}
}
}
I'd like return an object (ExpandoObject) with only the fields and nested fields received by the method.
var fieldsToGet = new List<string> { "FirstName", "Id"};
When I do this :
.Select(x => Helpers.FilteringProperties(x, fieldsToGet))
I receive an object with this two values, that's work.
I receive an object with FirstName and Id
Now I'd like return some properties of the nested object :
var fieldsToGet = new List<string> { "FirstName", "Id", "Language.Name"};
I'd like receive these properties :
FirstName, Id and Language.Name
The code below works but I'd like stay generic enough and be able to manage the nested.
How can I do this generic, managed the nested object ?
Thanks,
The current code :
public static object FilteringProperties(object employee, List<string> fields)
{
if (!fields.Any())
return employee;
else
{
ExpandoObject result = new ExpandoObject();
foreach (var field in fields)
{
var fieldValue = employee.GetType()
.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(employee, null);
((IDictionary<String, Object>)result).Add(field, fieldValue);
}
return result;
}
}
Sample classes :
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Language Language { get; set; }
public int LanguageId { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
I suppose you could do a split on the "."-character, get the Property with that name, and then get its value by calling your own function recursively.
Something like this (pseudo-code, can be a lot better)
if (field.Contains(".")) {
var parts = field.Split('.');
var fieldName = parts[0];
List<string> toGet = new List<string>();
toGet.Add(parts[1]); // this now contains everything after the "."
var fieldValue = employee.GetType()
.GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(employee, null);
((IDictionary<String, Object>)result).Add(field, FilteringProperties(fieldValue, toGet)
}
Using a recursive approach. I'm sure it can be improved.
public static object FilteringProperties(object employee, List<string> fields)
{
if (!fields.Any())
return employee;
else
{
ExpandoObject result = new ExpandoObject();
foreach (var field in fields)
{
object fieldValue = null;
Regex regex = new Regex("(\\w+)\\.(\\w+)");
Match match = regex.Match(field);
if (match.Success)
{
string className = match.Groups[1].Value;
string propertyName = match.Groups[2].Value;
var o = FilteringProperties(employee.GetType().GetProperty(className).GetValue(employee, null), new List<string>() {propertyName});
var entry = (IDictionary<string, object>) o;
fieldValue = entry[propertyName];
}
if(fieldValue == null)
fieldValue = employee.GetType()
.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(employee, null);
((IDictionary<String, Object>)result).Add(field, fieldValue);
}
return result;
}
}
Something like this seems to work for me -
public static ExpandoObject CreateObject(object parent, List<string> fields)
{
var expando = new ExpandoObject();
var ret = (IDictionary<string,object>)expando;
foreach (var property in fields)
{
//split to determine if we are a nested property.
List<string> properties = property.Split('.').ToList();
if (properties.Count() > 1)
{
//get our 'childs' children - ignoring the first item
var grandChildren = properties.Skip(1);
//copy this child object and use it to pass back into this method recusivly - thus creating our nested structure
var child = parent.GetType().GetProperty(properties[0]).GetValue(parent, null);
//passing in the child object and then its children - which are grandchildren from our parent.
ret.Add(properties[0], CreateObject(child, grandChildren.ToList()));
}
else //no nested properties just assign the property
ret.Add(property, parent.GetType().GetProperty(property).GetValue(parent));
}
return expando;
}
Thanks to Marc - guru from this site - I came up with the following approach:
public static object GetFlattenPropertyValue(this object source, string flattenPropertyName) {
var expression = source.GetType().CreatePropertyExpression(flattenPropertyName);
if (expression != null) {
var getter = expression.Compile();
return getter.DynamicInvoke(source);
}
return null;
}
public static LambdaExpression CreatePropertyExpression(this Type type, string flattenPropertyName) {
if (flattenPropertyName == null) {
return null;
}
var param = Expression.Parameter(type, "x");
Expression body = param;
foreach (var member in flattenPropertyName.Split('.')) {
body = Expression.PropertyOrField(body, member);
}
return Expression.Lambda(body, param);
}
I have JSON specified by a restful service I am consuming that looks like this as an example:
{
"id": "97",
"name": "Tom Production",
"description": null,
"parameters": [
{
"first_parameter_name": "First Parameter Value"
},
{
"second_parameter_name": "Second Parameter Value"
}
]
}
Note that the property names id, name, description, and parameters are all established as part of the specification. The collection of generic parameters, shown in my example as "first_parameter_name" and "second_parameter_name" are not specified.... could be anything and I want to map them to generically typed objects.
I have declared an object for this as:
[DataContract (Name = "MyClass")]
public class MyClass
{
[DataMember (Name = "id")]
public string Id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "description")]
public string Description { get; set; }
[DataMember(Name = "parameters")]
public List<object> Parameters { get; set; }
}
Serialization works fine, exactly as I expect:
var myObject = new MyClass();
myObject.Id = "97";
myObject.Name = "Tom Production";
myObject.Parameters = new List<object>();
myObject.Parameters.Add(new { first_parameter_name = "First Parameter Value" });
myObject.Parameters.Add(new { second_parameter_name = "Second Parameter Value" });
string json = JsonConvert.SerializeObject(myObject);
Console.WriteLine(json);
yields the JSON I am looking for, exactly as at the top of this posting.
HOWEVER.
Deserialization does NOT work fine. If it worked the way I hope it to, which would be to create generic types just like I had created, and the following code should work.... but instead it throws a reflection exception:
var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);
foreach (object o in myNewObject.Parameters)
{
Type t = o.GetType();
Console.WriteLine("\tType is {0}", t);
foreach (PropertyInfo pi in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
Console.WriteLine("\t\tName is {0}", pi.Name);
Console.WriteLine("\t\tValue is {0}", pi.GetValue(o, null));
}
}
Instead, I have to write code that is Newtonsoft-specific (ick) to use a kind of fake Newtonsoft reflection:
var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);
foreach (object o in myNewObject.Parameters)
{
var jo = o as JObject;
if (jo != null)
{
foreach (JProperty prop in jo.Properties())
{
Console.WriteLine("\t\tName is {0}", prop.Name);
Console.WriteLine("\t\tValue is {0}", prop.Value);
}
}
}
Is there a way that I can control the Deserializer so that it will generate the proper generic types rather than the JObject type with the JProperties?
Many thanks in advance.
JObjects map most directly to Dictionary<string, object>s, since they're each simply a collection of keys to values. If you know that the value is always a string, you can make it a Dictionary<string, string>.
[DataMember(Name = "parameters")]
public List<Dictionary<string, object>> Parameters { get; set; }
// or
[DataMember(Name = "parameters")]
public List<Dictionary<string, string>> Parameters { get; set; }
// e.g.
var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);
foreach (var dict in myNewObject.Parameters)
{
foreach (var pair in dict)
{
Console.WriteLine("\t\tKey is {0}", pair.Key);
Console.WriteLine("\t\tValue is {0}", pair.Value);
}
}
I am calling Bloomberg Server API (for Stock Market Data) and getting Data back in a Dictionary<string, object> Where the Key to the dictionary is the Field Name on Bloomberg's side, and the object contains the data value from Bloomberg and can be string, decimal, DateTime, boolean etc.
After I get the Bloomberg Data, I need to populate my strong type entities / classes with the values returned. Depending on what Field Names I send in my request to bloomberg, the returned Dictionary could have different key values. The problem I am having is, the bloomberg field name and my .net entity's property names don't match, so I am not sure I can do this mapping using AutoMapper or a similar library.
I also tried using a Tuple<string,string,object> where the 1st tuple item is the bloomberg field name, the 2nd tuple item is my entity's property name and the 3rd tuple item is the data value returned from bloomberg. That is not working out either (so far), so I am wondering if there is a simple straight-forward way of maintaining this bloombergfield<-->EntityProperty mapping and populate the Entity's value using the Bloomberg Data Value for the respective field. A Generic (i.e. using C# Generics) solution would be even better!
I have pasted below sample console app code so you can paste it and try it out. The 2 dictionaries, 1 for stockdata and other for bonddata have fake data, but you get the idea. I have also added comments below to re-iterate what I am trying to accomplish.
Thanks!!
namespace MapBBFieldsToEntityProperties
{
using System;
using System.Collections.Generic;
class Program
{
public class StockDataResult
{
public string Name { get; set; }
public decimal LastPrice { get; set; }
public DateTime SettlementDate { get; set; }
public decimal EPS { get; set; }
}
public class BondDataResult
{
public string Name { get; set; }
public string Issuer { get; set; }
public decimal Duration { get; set; }
public DateTime YieldToMaturity { get; set; }
}
static void Main(string[] args)
{
// Data Coming from Bloomberg.
// Dictionary Key is the Bloomberg Data Field Name.
// Dictionary Object is the Value returns and can be any .Net primitive Type
// Sample Data returned for a Stock Query to Bloomberg
Dictionary<string, object> dctBloombergStockData
= new Dictionary<string, object>
{
{ "NAME", "IBM" },
{ "PX_LAST", 181.30f },
{ "SETTLE_DT", "11/25/2013" } // This is Datetime value
};
// Sample Data returned for a Bond Query to Bloomberg
Dictionary<string, object> dctBloombergBondData =
new Dictionary<string, object>
{
{ "NAME", "IBM" },
{ "ISSUE_ORG","IBM Corp" },
{ "DURATION", 4.430f },
{ "YLD_TO_M", 6.456f }
};
// This is my Stock Entity
StockDataResult stockData = new StockDataResult();
// This is my Bond Entity
BondDataResult bondData = new BondDataResult();
// PROBLEM STATEMENT:
//
// Need to somehow Map the Data returned from Bloomberg into the
// Corresponding Strong-typed Entity for that Data Type.
// i.e.
// map dctBloombergStockData to stockData Entity instance as follows
//
// dctBloombergStockData."NAME" Key <--------> stockData.Name Property so that
// dctBloombergStockData["NAME"] value of "IBM" can be assigned to stockData.Name
//
// dctBloombergStockData."PX_LAST" Key <--------> stockData.LastPrice Property so that
// dctBloombergStockData["PX_LAST"] value 181.30f can be assigned to stockData.LastPrice value.
// ....
// .. you get the idea.
// Similarly,
// map dctBloombergBondData Data to bondData Entity instance as follows
//
// dctBloombergBondData."NAME" Key <--------> bondData.Name Property so that
// dctBloombergBondData["NAME"] value of "IBM" can be assigned to bondData.Name property's value
//
// dctBloombergBondData."ISSUE_ORG" Key <--------> bondData.Issuer Property so that
// dctBloombergBondData["ISSUE_ORG"] value 181.30f can be assigned to bondData.Issuer property's value.
//
// dctBloombergBondData."YLD_TO_M" Key <--------> bondData.YieldToMaturity Property so that
// dctBloombergBondData["YLD_TO_M"] value 181.30f can be assigned to bondData.YieldToMaturity property's value.
}
}
}
I am sure quite a few improvements are possible, but this is one way of specifying a mapping and using that map.
class Program
{
public class Mapper<TEntity> where TEntity : class
{
private readonly Dictionary<string, Action<TEntity, object>> _propertyMappers = new Dictionary<string, Action<TEntity, object>>();
private Func<TEntity> _entityFactory;
public Mapper<TEntity> ConstructUsing(Func<TEntity> entityFactory)
{
_entityFactory = entityFactory;
return this;
}
public Mapper<TEntity> Map<TProperty>(Expression<Func<TEntity, TProperty>> memberExpression, string bloombergFieldName, Expression<Func<object, TProperty>> converter)
{
var converterInput = Expression.Parameter(typeof(object), "converterInput");
var invokeConverter = Expression.Invoke(converter, converterInput);
var assign = Expression.Assign(memberExpression.Body, invokeConverter);
var mapAction = Expression.Lambda<Action<TEntity, object>>(
assign, memberExpression.Parameters[0], converterInput).Compile();
_propertyMappers[bloombergFieldName] = mapAction;
return this;
}
public TEntity MapFrom(Dictionary<string, object> bloombergDict)
{
var instance = _entityFactory();
foreach (var entry in bloombergDict)
{
_propertyMappers[entry.Key](instance, entry.Value);
}
return instance;
}
}
public class StockDataResult
{
public string Name { get; set; }
public decimal LastPrice { get; set; }
public DateTime SettlementDate { get; set; }
public decimal EPS { get; set; }
}
public static void Main(params string[] args)
{
var mapper = new Mapper<StockDataResult>()
.ConstructUsing(() => new StockDataResult())
.Map(x => x.Name, "NAME", p => (string)p)
.Map(x => x.LastPrice, "PX_LAST", p => Convert.ToDecimal((float)p))
.Map(x => x.SettlementDate, "SETTLE_DT", p => DateTime.ParseExact((string)p, "MM/dd/yyyy", null));
var dctBloombergStockData = new Dictionary<string, object>
{
{ "NAME", "IBM" },
{ "PX_LAST", 181.30f },
{ "SETTLE_DT", "11/25/2013" } // This is Datetime value
};
var myStockResult = mapper.MapFrom(dctBloombergStockData);
Console.WriteLine(myStockResult.Name);
Console.WriteLine(myStockResult.LastPrice);
Console.WriteLine(myStockResult.SettlementDate);
}
}
As you have said your self you need a mapping table. You can create a static read only dictionary in you type that would map each key filed returned from Bloomberg to property in your strongly typed class.
Here is how I would do it.
PS: I used linqpad to test.
PPS: You can add as many mapper(s) to the dictionary as need be. You also need fast-member to run this code.
void Main()
{
var dctBloombergStockData = new Dictionary<string, object>
{
{ "NAME", "IBM" },
{ "PX_LAST", 181.30f },
{ "SETTLE_DT", "11/25/2013" } // This is Datetime value
};
StockDataResult.FromBloombergData(dctBloombergStockData);
}
// Define other methods and classes here
interface IMapper
{
string PropertyName { get; }
object Parse(object source);
}
class Mapper<T, TResult> : IMapper
{
private Func<T, TResult> _parser;
public Mapper(string propertyName, Func<T, TResult> parser)
{
PropertyName = propertyName;
_parser = parser;
}
public string PropertyName { get; private set; }
public TResult Parse(T source)
{
source.Dump();
return _parser(source);
}
object IMapper.Parse(object source)
{
source.Dump();
return Parse((T)source);
}
}
public class StockDataResult
{
private static TypeAccessor Accessor = TypeAccessor.Create(typeof(StockDataResult));
private static readonly Dictionary<string, IMapper> Mappers = new Dictionary<string, IMapper>(StringComparer.CurrentCultureIgnoreCase){
{ "NAME", new Mapper<string, string>("Name", a => a) },
{ "PX_LAST", new Mapper<float, decimal>("LastPrice", a => Convert.ToDecimal(a)) },
{ "SETTLE_DT", new Mapper<string, DateTime>("SettlementDate", a => DateTime.ParseExact(a, "MM/dd/yyyy", null)) }
};
protected StockDataResult()
{ }
public string Name { get; set; }
public float LastPrice { get; set; }
public DateTime SettlementDate { get; set; }
public decimal EPS { get; set; }
public static StockDataResult FromBloombergData(Dictionary<string, object> state)
{
var result = new StockDataResult();
IMapper mapper;
foreach (var entry in state)
{
if(!Mappers.TryGetValue(entry.Key, out mapper))
{ continue; }
Accessor[result, mapper.PropertyName.Dump()] = mapper.Parse(entry.Value);
}
return result;
}
}
How about:
stockData.Name = dctBloombergStockData["NAME"];
stockData.LastPrice = dctBloombergStockData["PX_LAST"]
//and so on...