Custom Mapping with AutoMapper - c#

I have two very simple objects:
public class CategoryDto
{
public string Id { get; set; }
public string MyValueProperty { get; set; }
}
public class Category
{
public string Id { get; set; }
[MapTo("MyValueProperty")]
public string Key { get; set; }
}
When mapping a Category to a CategoryDto with AutoMapper, I would like the following behavior:
The properties should be mapped as usual, except for those that have the MapTo attribute. In this case, I have to read the value of the Attribute to find the target property. The value of the source property is used to find the value to inject in the destination property (with the help of a dictionary). An example is always better that 1000 words...
Example:
Dictionary<string, string> keys =
new Dictionary<string, string> { { "MyKey", "MyValue" } };
Category category = new Category();
category.Id = "3";
category.Key = "MyKey";
CategoryDto result = Map<Category, CategoryDto>(category);
result.Id // Expected : "3"
result.MyValueProperty // Expected : "MyValue"
The Key property is mapped to the MyValueProperty (via the MapTo Attribute), and the assigned value is "MyValue", because the source property value is "MyKey" which is mapped (via dictionary) to "MyValue".
Is this possible using AutoMapper ? I need of course a solution that works on every object, not just on Category/CategoryDto.

I finally (after so many hours !!!!) found a solution.
I share this with the community; hopefully it will help someone else...
Edit: Note that it's now much simpler (AutoMapper 5.0+), you can do like I answered in this post: How to make AutoMapper truncate strings according to MaxLength attribute?
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> MapTo<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
Type sourceType = typeof(TSource);
Type destinationType = typeof(TDestination);
TypeMap existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType);
string[] missingMappings = existingMaps.GetUnmappedPropertyNames();
if (missingMappings.Any())
{
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (string property in missingMappings)
{
foreach (PropertyInfo propertyInfo in sourceProperties)
{
MapToAttribute attr = propertyInfo.GetCustomAttribute<MapToAttribute>();
if (attr != null && attr.Name == property)
{
expression.ForMember(property, opt => opt.ResolveUsing(new MyValueResolve(propertyInfo)));
}
}
}
}
return expression;
}
}
public class MyValueResolve : IValueResolver
{
private readonly PropertyInfo pInfo = null;
public MyValueResolve(PropertyInfo pInfo)
{
this.pInfo = pInfo;
}
public ResolutionResult Resolve(ResolutionResult source)
{
string key = pInfo.GetValue(source.Value) as string;
string value = dictonary[key];
return source.New(value);
}
}

This should be fairly straightforward using an implementation of IValueResolver and the ResolveUsing() method. You basically just need to have a constructor for the resolver that takes in the property info (or if you wanna be fancy that takes in a lambda expression and resolves the property info similar to How to get the PropertyInfo of a specific property?. Though I haven't tested it myself I imagine the following would work:
public class PropertyBasedResolver : IValueResolver
{
public PropertyInfo Property { get; set; }
public PropertyBasedResolver(PropertyInfo property)
{
this.Property = property;
}
public ResolutionResult Resolve(ResolutionResult source)
{
var result = GetValueFromKey(property, source.Value); // gets from some static cache or dictionary elsewhere in your code by reading the prop info and then using that to look up the value based on the key as appropriate
return source.New(result)
}
}
Then to set up that mapping you need to do:
AutoMapper.Mapper.CreateMap<Category, CategoryDto>()
.ForMember(
dest => dest.Value,
opt => opt.ResolveUsing(
src =>
new PropertyBasedResolver(typeof(Category.Key) as PropertyInfo).Resolve(src)));
Of course that is a pretty gross lamda and I would suggest that you clean it up by having your property resolver determine the property on the source object it should look at based on the attribute/property info so you can just pass a clean new PropertyBasedResolver(property) into the ResolveUsing() but hopefully this explains enough to put you on the right track.

Lets assume I have the following classes
public class foo
{
public string Value;
}
public class bar
{
public string Value1;
public string Value2;
}
You can pass a lambda to ResolveUsing:
.ForMember(f => f.Value, o => o.ResolveUsing(b =>
{
if (b.Value1.StartsWith("A"));)
{
return b.Value1;
}
return b.Value2;
}
));

Related

Why do I have to Ignore all other properties in AutoMapper when using records?

public record Destination(double X, double Y);
public struct Source
{
public double X { get; set; }
public Potato Potato { get; set; }
public double Z { get; set; }
}
public struct Potato
{
public double Y { get; set; }
}
public MappingProfile()
{
CreateMap<Source, Destination>();
.ForCtorParam(nameof(Destination.Y), e => e.MapFrom(x => x.Potato.Y))
.ForAllOtherMembers(x => x.Ignore());
}
To map source to destination, I need to manually map one of the children.
However, automapper will then give an extremly confusing message, saying Y property is unmapped.
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
==========================================================================================================
Source -> Destination (Destination member list)
Unmapped properties:
Y
I found by adding the line to ignore all other members, it would 'solve' the issue. Is there a better way of preventing this error occuring?
The error message mentions mapping all the constructor parameters, but even if I add .ForCtorParam(nameof(Destination.X), e => e.MapFrom(x => x.X)) the error still occurs.
This is already solved in 11. Because you cannot upgrade, you'll have to ignore all those properties. The problem is that the properties have setters and in AM 10 the properties are not considered mapped even if they're already mapped through the constructor.
Another solution is to use a struct (or class) without setters instead of record.
I created the following class to help cover my needs, since I can't upgrade to newer AutoMapper versions.
It is naive, and assumes that all properties on a record should be mapped to constructor.
Benefits:
Will automatically map all properties with same name
Can use lambda arguments instead of nameof
Don't need to call ignore other members
Will throw exception if a property is not mapped
To use it, in MappingProfile:
new RecordMapBuilder<TSource, TDestination>(this)
.Map(x => x.Foo, x => x.Offset.Foo)
.Build();
public class RecordMapBuilder<TSource, TDestination>
{
private readonly Profile _profile;
private readonly bool _autoMapMatchingProperties;
private readonly Dictionary<string, Expression<Func<TSource, object>>> _ctorParamActions = new();
public RecordMapBuilder(Profile profile, bool autoMapMatchingProperties = true)
{
_profile = profile;
_autoMapMatchingProperties = autoMapMatchingProperties;
}
public void Build()
{
var map = _profile.CreateMap<TSource, TDestination>();
var unmappedDestinationProperties = new HashSet<string>(
typeof(TDestination)
.GetProperties()
.Where(e => !_ctorParamActions.ContainsKey(e.Name))
.Select(e => e.Name));
var sourceProperties = new HashSet<string>(typeof(TSource)
.GetProperties()
.Select(e => e.Name));
var mappableProperties = unmappedDestinationProperties.Intersect(sourceProperties).ToHashSet();
var unMappableProperties = unmappedDestinationProperties.Except(sourceProperties).ToHashSet();
if (unMappableProperties.Any())
{
var properties = string.Join(", ", unMappableProperties);
throw new InvalidOperationException($"Not all properties mapped for type {typeof(TSource)} -> {typeof(TDestination)}: {properties}");
}
if (_autoMapMatchingProperties)
{
foreach (var name in mappableProperties)
{
map.ForCtorParam(name, x => x.MapFrom(name));
}
}
foreach (var kv in _ctorParamActions)
{
map.ForCtorParam(kv.Key, x => x.MapFrom(kv.Value));
}
map.ForAllOtherMembers(x => x.Ignore());
}
public RecordMapBuilder<TSource, TDestination> Map(Expression<Func<TDestination, object>> destination, Expression<Func<TSource, object>> source)
{
var name = GetName(destination);
_ctorParamActions[name] = source;
return this;
}
private static string GetName(Expression<Func<TDestination, object>> destination)
{
{
if (destination.Body is UnaryExpression ue && ue.Operand is MemberExpression me)
{
return me.Member.Name;
}
}
{
if (destination.Body is MemberExpression me)
{
return me.Member.Name;
}
}
throw new InvalidOperationException($"Unhandled expression of type: {destination.Body.GetType()}");
}

Mapper supporting both : "mapping from xml" and "unflattening"

rI know two tools : Value injector and Automapper. Each of them is currently supporting one of the feature, that i realy need. :) Background: i'm using stored procedure to load data from DB. relations 1 to many are handled as XML properties. Let's consider following example
public class AutorDto
{
public string Name { get; set; }
public List<Post> Posts { get; set; }
public ICity City { get; set; }
}
public interface ICity
{
string Name { get; set; }
string Code { get; set; }
}
public class CityDto
{
public string Name { get; set; }
public string Code { get; set; }
}
public class PostDto
{
public DateTime Date { get; set; }
public string Content { get; set; }
}
I have stored procedure, that will return me this structure in following schema :
public class Autor_From_Stored_Procedure
{
public string Name { get; set; }
public string Posts { get; set; }
public string CityName { get; set; }
public string CityCode { get; set; }
}
In order to map Autor_From_Stored_Procedure to AutorDto we must handle two topics:
Deserializing from XML (i've managed to handle this problem using automapper. I've used this topic : Automapper to create object from XML) and this article : http://www.codeproject.com/Articles/706992/Using-AutoMapper-with-Complex-XML-Data
Unflattening. It seems, that AutoMapper is not supporting convention based unflattening. Is, that true ? I saw, that there is posibility to declare some string (e.g. "City") as Unflattened object and everything should work - but value injector offers this as a convetion based standard:
objectToIll.InjectFrom < UnflatLoopInjection>(object) - without neccessity to declare which properties [names in specific] would be deflattened) Is this also possible with automapper ?
If not, then maybe i should focus on value injector. If so - the problem from the 1) point is still valid (hopefully it is solved as easly as in automapper)
Penny for your thoughts!
##Update
I've changed Dto definitions adding interface to City (as this is my case)
I will post anwser to my question - maybe it will help someone. I've moved from Automapper to Valueinjecter. Xml binding was done using custom AddMap() and using XmlSerializer (no magic here)
But it turns out, that there will be problem with Interface on "Autor" class (and deflattening) You cannot simply create interface instance and this was the problem. I've modified slightly valueinjecter Tunnelier class to handle such case.
Firstly i've tried to cover this using somehow convention (if you find property ISomething, cut "I" add "Dto" But it is not elegant solution. So i ended up with custom : Inteface+Class mapping (so i'm pointing out : if you see ISomething interface, create instance of SomethingDto) Here is the modified code (maybe this could be added to valueinjecter implementation ?) The main "Mapper" class is not partial so i've defined new class for those mappings:
namespace Omu.ValueInjecter
{
public partial class MapperActivations
{
public static ConcurrentDictionary<Type, Type> InterfaceActivations = new ConcurrentDictionary<Type, Type>();
public static void AddInterfaceActivation<Interface, Class>()
{
InterfaceActivations.AddOrUpdate(typeof(Interface), typeof(Class), (key, oldValue) => typeof(Class));
}
}
}
Here are modified valueInjected classes:
public static class TunnelierCustom
{
public static PropertyWithComponent Digg(IList<string> trail, object o)
{
var type = o.GetType();
if (trail.Count == 1)
{
return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]) };
}
var prop = type.GetProperty(trail[0]);
var val = prop.GetValue(o);
if (val == null)
{
if (prop.PropertyType.IsInterface)
{
if (MapperActivations.InterfaceActivations.ContainsKey(prop.PropertyType))
{
val = Activator.CreateInstance(MapperActivations.InterfaceActivations[prop.PropertyType]);
}
else
{
throw new Exception("Unable to create instance of: " + prop.PropertyType.Name + ". Are you missing InterfaceActivations bidning? Please add it using MapperActivations.AddInterfaceActivation<Interface, Class>() statement");
}
}
else
{
val = Activator.CreateInstance(prop.PropertyType);
}
prop.SetValue(o, val);
}
trail.RemoveAt(0);
return Digg(trail, val);
}
public static PropertyWithComponent GetValue(IList<string> trail, object o, int level = 0)
{
var type = o.GetType();
if (trail.Count == 1)
{
return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]), Level = level };
}
var prop = type.GetProperty(trail[0]);
var val = prop.GetValue(o);
if (val == null) return null;
trail.RemoveAt(0);
return GetValue(trail, val, level + 1);
}
}
/// <summary>
/// performs flattening and unflattening
/// first version of this class was made by Vadim Plamadeala ☺
/// </summary>
public static class UberFlatterCustom
{
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match, StringComparison comparison)
{
var trails = TrailFinder.GetTrails(flatPropertyName, target.GetType().GetProps(), match, comparison, false).Where(o => o != null);
return trails.Select(trail => TunnelierCustom.Digg(trail, target));
}
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match)
{
return Unflat(flatPropertyName, target, match, StringComparison.Ordinal);
}
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target)
{
return Unflat(flatPropertyName, target, (upn, pi) => upn == pi.Name);
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match)
{
return Flat(flatPropertyName, source, match, StringComparison.Ordinal);
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match, StringComparison comparison)
{
var trails = TrailFinder.GetTrails(flatPropertyName, source.GetType().GetProps(), match, comparison).Where(o => o != null);
return trails.Select(trail => TunnelierCustom.GetValue(trail, source));
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source)
{
return Flat(flatPropertyName, source, (up, pi) => up == pi.Name);
}
}
public class UnflatLoopCustomInjection : ValueInjection
{
protected override void Inject(object source, object target)
{
var sourceProps = source.GetType().GetProps();
foreach (var sp in sourceProps)
{
Execute(sp, source, target);
}
}
protected virtual bool Match(string upn, PropertyInfo prop, PropertyInfo sourceProp)
{
return prop.PropertyType == sourceProp.PropertyType && upn == prop.Name;
}
protected virtual void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
{
tp.SetValue(target, sp.GetValue(source));
}
protected virtual void Execute(PropertyInfo sp, object source, object target)
{
if (sp.CanRead)
{
var endpoints = UberFlatterCustom.Unflat(sp.Name, target, (upn, prop) => Match(upn, prop, sp)).ToArray();
foreach (var endpoint in endpoints)
{
SetValue(source, endpoint.Component, sp, endpoint.Property);
}
}
}
}
And this is example use :
MapperActivations.AddInterfaceActivation<ICity, City>();
Mapper.AddMap<Auto_From_Stored_Procedure, AutorDto>(src =>
{
var res = new User();
res.InjectFrom<UnflatLoopCustomInjection>(src);
res.Posts = Mapper.Map<XElement, List<Posts>>(src.PostsXml , "PostDto"); //this is mapping using XmlSerializer, PostsXml is XElement.Parse(Posts) in Autor_From_Stored_Procedure.
return res;
});
new version has been released 3.1
you can now specify an activator parameter for the UnflatLoopInjection
have a look at this unit test

Custom attributes in c# for data types

I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName" of the property. This way, we could then have a method that takes the data reader as a param, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.
private static App GetAppInfo(SqlDataReader dr)
{
App app = new App();
app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");
return app;
}
What I am thinking though, so if I had a class for example like so:
[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}
How could I go about this?
Fist of all, this is possible and if you like I could add a code sample.
But: This is not a good idea.
Why, you ask?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType which is a System.Type object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName") that does the logic for your.
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
If you combine that you can achive what you want with
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET, give it a try. https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
Run the test and enjoy.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}

Copying All Class Fields and Properties To Another Class

I have a class which normally contains Fields, Properties. What i want to achieve is instead of this:
class Example
{
public string Field = "EN";
public string Name { get; set; }
public int? Age { get; set; }
public List<string> A_State_of_String { get; set; }
}
public static void Test()
{
var c1 = new Example
{
Name = "Philip",
Age = null,
A_State_of_String = new List<string>
{
"Some Strings"
}
};
var c2 = new Example();
//Instead of doing that
c2.Name = string.IsNullOrEmpty(c1.Name) ? "" : c1.Name;
c2.Age = c1.Age ?? 0;
c2.A_State_of_String = c1.A_State_of_String ?? new List<string>();
//Just do that
c1.CopyEmAll(c2);
}
What i came up with but doesn't work as expected.
public static void CopyEmAll(this object src, object dest)
{
if (src == null) {
throw new ArgumentNullException("src");
}
foreach (PropertyDescriptor item in TypeDescriptor.GetProperties(src)) {
var val = item.GetValue(src);
if (val == null) {
continue;
}
item.SetValue(dest, val);
}
}
Problems:
Although i checked for null, it seems to bypass it.
Doesn't seem to copy Fields.
Notes:
I don't want to use AutoMapper for some technical issues.
I want the method to copy values and not creating new object. [just mimic the behavior i stated in the example]
I want the function to be recursive [if the class contains another classes it copies its values too going to the most inner one]
Don't want to copy null or empty values unless i allow it to.
Copies all Fields, Properties, or even Events.
Based on Leo's answer, but using Generics and copying also the fields:
public void CopyAll<T>(T source, T target)
{
var type = typeof(T);
foreach (var sourceProperty in type.GetProperties())
{
var targetProperty = type.GetProperty(sourceProperty.Name);
targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
}
foreach (var sourceField in type.GetFields())
{
var targetField = type.GetField(sourceField.Name);
targetField.SetValue(target, sourceField.GetValue(source));
}
}
And then just:
CopyAll(f1, f2);
You can use serialization to serialize object A and deserialize as object B - if they have very same structure, you can look here for object deep copy.
Deep cloning objects
I know you don't want to use Automapper, but if the types have only SIMILAR structure, you should maybe use Automapper which is based on reflection. You can download a nuget and find some information here:
https://www.nuget.org/packages/AutoMapper/
your code then will look like
public TOutput CopyAll<TInput, TOutput>(TInput input)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<TInput, TOutput>());
IMapper mapper = config.CreateMapper();
return mapper.Map<TOutput>(vstup);
}

Get value from Dictionary holding generic type in C#

public class Table<T> where T:SomeClassWithIntegerID
{
private Dictionary<int, T> map = new Dictionary<int, T>();
public bool isInMemory(int id)
{
if (map.ContainsKey(id))
return true;
return false;
}
public T setIt(T obj)
{
map[obj.id] = obj;
}
public T getIt(int id)
{
return map[id];
}
}
Example:
private static Table<User> table = new Table<User>;
class User : SomeClassWithIntegerID
{
public string name { get; set; }
public string password { get; set; }
}
class SomeClassWithIntegerID
{
public int id { get; set; }
}
I can now check if the Table holds a user with a certain ID, because I use that as the key, but there is now no way for me to check if the Table holds a User with the name Bob or whatever. I want to be able to do something like table.isInMemory(name, "bob") but how is that possible with a generic type?
I need to create a function that allows the end user to specify the field and expected value of said field, after which Table will go over all objects of that class, stored in the Dictionary, to see if one has the field that matches that value.
Is this possible at all?
public bool IsInMemory(Func<T, bool> predicate)
{
return map.Values.Any(predicate);
}
You can then call it as:
table.IsInMemory(u => u.Name == "bob");
If you want to use a property name and value to match on you could add an overload:
public bool IsInMemory(string propertyName, object value)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
if(property == null) throw new ArgumentException("Invalid property name: " + propertyName);
var predicate = new Func<T, bool>(item => object.Equals(value, property.GetValue(item, null)));
return IsInMemory(predicate);
}
I would complement Lee's answer with a Where-method to enable querying with LINQ:
public IEnumerable<T> Where(Func<T, bool> predicate)
{
return map.Values.Where(predicate);
}
And an example:
table.Where(x => x.name.Contains("natli"))
.OrderBy(x => x.name);
To answer your actual question, you can (if you're using .NET 4.0) use the dynamic type, which resolves all methods and such at runtime, to call methods or properties that the compiler doesn't know about from its context.
dynamic dynObject = someObject;
dynObject.SomeMethod("Hi there", 27); // Call whatever method or property you "know" exist

Categories