How to create generic mapping with AutoMapper and ConstructUsing - c#

I have a map configuration:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<IDictionary<int, MyType1>, List<MyType1>>().ConstructUsing(
x0 => x0?.OrderBy(x => x.Key).Select(x => x.Value).ToList());
});
How can I change this to work with "all MyTypes"?
I want to convert from IDictionary<int, T> to List<T> for any T type.
I found a very ugly solution:
cfg.CreateMap(typeof(IDictionary<,>), typeof(List<>)).ConstructUsing(
x0 =>
{
if (x0 == null)
{
return null;
}
var dict = (IEnumerable)x0;
var type = x0.GetType().GetGenericArguments()[1];
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
CastDictionaryEntry(dict).OrderBy(x => x.Key).ForEach(x => list.Add(x.Value));
return list;
}
);
and:
static IEnumerable<DictionaryEntry> CastDictionaryEntry(IEnumerable dict)
{
foreach (var item in dict)
{
yield return (DictionaryEntry)item;
}
}
Is there any easier way to do this?

I think works, but then again I haven't tested against a broad set of inputs-
class ABC
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
}
static List<T> ConfigureMap<T>(Dictionary<int, T> input) where T : class
{
AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap(input.GetType(), typeof(List<T>))
.ConstructUsing(Construct));
return Mapper.Map<List<T>>(input);
}
private static object Construct(object arg1, ResolutionContext arg2)
{
if (arg1 != null)
{
var val = arg1 as IDictionary;
var argument = val.GetType().GetGenericArguments()[1];
if (val != null)
{
var type = Activator.CreateInstance(typeof(List<>).MakeGenericType(argument));
foreach (var key in val.Keys)
{
((IList)type).Add(val[key]);
}
return type;
}
}
return null;
}
var so = new Dictionary<int, ABC> { { 1, new ABC { MyProperty = 10, MyProperty2 = 30 } } };
var dest = new List<ABC>();
var res = ConfigureMap<ABC>(so);

Related

Is it possible to add GroupBy to a IQueryable<T> query?

I am trying to genericize some repeated stuff but struggling with this. I don't even know if it's even possible. I put what I need inside the code block but I'd be happy to clarify if need be. Any help is appreciated.
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
public static object GetPropertyValue(this object obj, string name)
{
foreach (string part in name.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
public static T GetPropertyValue<T>(this object obj, string name)
{
object retval = GetPropertyValue(obj, name);
if (retval == null) { return default(T); }
// throws InvalidCastException if types are incompatible
return (T)retval;
}
#endregion
Thanks coder_b, I used another response from the link you provided and got what I am looking for. Here is the answer if anyone else needs it:
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
//var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
//Answer
var Groupped = query.GroupBy(GetGroupKey<T>(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).ToList();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
private static Expression<Func<T, string>> GetGroupKey<T>(string property)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<T, string>>(body, parameter);
}
#endregion

How to use attribute to map properties

I have the following code.
public class SyncProperty : Attribute
{
public readonly string PropertyName;
public SyncProperty(string propertyName)
{
this.PropertyName = propertyName;
}
}
public class SyncContact
{
[SyncProperty("first_name")]
public string FirstName { get; set; }
[SyncProperty("last_name")]
public string LastName { get; set; }
[SyncProperty("phone")]
public string Phone { get; set; }
[SyncProperty("email")]
public string Email { get; set; }
}
I need to create an instance of my SyncContact such as
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
And then with that object I need to create a NameValueCollection where the object's property uses the SyncProperty's PropertyName as the Key in the collection. Then use that to make a post request to an API. So in this case I would end up with a collection like...
collection["first_name"] = "Test"
collection["last_name"] = "Person"
collection["phone"] = "123-123-1234"
collection["email"] = "test#test.com"
How can I do that?
If you want to get some type metadata, you should use Reflection. To read attribute you can use GetCustomAttribute<AttributeType>() extension for MemberInfo.
This extension method builds sync dictionary from type properties decorated with SyncPropertyAttribute (I suggest to use the dictionary instead of NameValueCollection):
public static Dictionary<string, string> ToSyncDictionary<T>(this T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new {
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
Usage:
var collection = contact.ToSyncDictionary();
Output:
{
"first_name": "Test",
"last_name": "Person",
"phone": "123-123-1234",
"email": "test#test.com"
}
Note: if you are going to use contact data in POST request, then you should consider using simple JSON serialization attributes instead of creating your own attributes. E.g. with Json.NET:
public class SyncContact
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
}
Then simple serialization will do the job:
string json = JsonConvert.SerializeObject(contact);
And the result will be exactly same as above.
The attributes belong to the properties of the class, so you need to get the type of the class, then find the appropriate properties and then get the custom attributes.
Something like:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
var t = typeof(SyncContact);
var props = t.GetProperties().Select(p => new { p, attr = p.GetCustomAttribute<SyncProperty>() }).Where(p => p.attr != null);
var dict = props.ToDictionary(p => p.attr.PropertyName, v => v.p.GetValue(contact) );
This leaves out any properties that aren't tagged, but you can decide to handle those differently if you wanted to.
Fiddle
Assuming SyncProperty is tagged on every property, this should do the job:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
var collection = contact.GetType().GetProperties()
.Select(x => new
{
x.GetCustomAttribute<SyncProperty>().PropertyName,
Value = x.GetValue(contact).ToString()
})
.ToDictionary(x => x.PropertyName, x => x.Value);
As a helper method:
public static class SynxHelper
{
public static Dictionary<string, string> Serialize<T>(T obj)
{
return typeof(T).GetProperties()
.Select(x => new
{
SyncProperty = x.GetCustomAttribute<SyncProperty>(),
Value = x.GetValue(obj)
})
.Where(x => x.SyncProperty != null)
.ToDictionary(x => x.SyncProperty.PropertyName, x => x.Value.ToString());
}
}
// usage
var collection = SynxHelper.Serialize(contact);
Here is a one line linq solution for this
(from prop in obj.GetType().GetProperties()
where prop.GetCustomAttribute<SyncProperty>() != null
select new { Key = prop.GetCustomAttribute<SyncProperty>().PropertyName, Value = prop.GetValue(obj) })
.ToDictionary(k => k.Key, v => v.Value);
BUT!!!!!!!! Don't try doing this your self. This is not optimized and slow as all reflection is.
This is just to demonstrate how bad reflection is
static void Main(string[] args)
{
var data = Enumerable.Range(0, 10000).Select(i => new SyncContact { FirstName = "Test", LastName = "Person", Phone = "123-123-1234", Email = "test#test.com" }).ToArray();
Stopwatch sw = new Stopwatch();
long m1Time = 0;
var total1 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary(item);
total1++;
}
sw.Stop();
m1Time = sw.ElapsedMilliseconds;
sw.Reset();
long m2Time = 0;
var total2 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary2(item);
total2++;
}
sw.Stop();
m2Time = sw.ElapsedMilliseconds;
Console.WriteLine($"ToSyncDictionary : {m1Time} for {total1}");
Console.WriteLine($"ToSyncDictionary2 : {m2Time} for {total2}");
Console.ReadLine();
}
public static IDictionary<string, string> ToSyncDictionary<T>(T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new
{
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
public static IDictionary<string, string> ToSyncDictionary2<T>(T value)
{
return Mapper<T>.ToSyncDictionary(value);
}
public static class Mapper<T>
{
private static readonly Func<T, IDictionary<string, string>> map;
static Mapper()
{
map = ObjectSerializer();
}
public static IDictionary<string, string> ToSyncDictionary(T value)
{
return map(value);
}
private static Func<T, IDictionary<string, string>> ObjectSerializer()
{
var type = typeof(Dictionary<string, string>);
var param = Expression.Parameter(typeof(T));
var newExp = Expression.New(type);
var addMethod = type.GetMethod(nameof(Dictionary<string, string>.Add), new Type[] { typeof(string), typeof(string) });
var toString = typeof(T).GetMethod(nameof(object.ToString));
var setData = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select Expression.ElementInit(addMethod,
Expression.Constant(name),
Expression.Condition(Expression.Equal(Expression.Property(param, p), Expression.Constant(null)),
Expression.Call(Expression.Property(param, p), toString),
Expression.Constant(null,typeof(string))));
return Expression.Lambda<Func<T, IDictionary<string, string>>>(Expression.ListInit(newExp, setData), param).Compile();
}
}
On my machine I got a 10x boost in pers.
If you can use some serializer like JSON.net since you will need to change a lot of things to make it work well and you already have a tone of stuff that dose it for you.

AutoMapper mapping unmapped properties to Dictionary / ExtensionData

How can I make AutoMapper to map missing unmapped properties to a dictionary inside the destination object? (Like ExtensionData during serialization)
Example:
class Source
{
public int A {get;set;}
public int B {get;set;}
public int C {get;set;}
}
class Destination
{
public int A {get;set;}
public Dictionary<string, object> D {get;set;}
}
Source s = new Source { A = 1, B = 2, C = 3 };
Destination d = ... // Mapping code
Now I want the following result:
d.A ==> 1
d.D ==> {{ "B", 2 }, { "C", 3 }}
* EDIT *
In the end I am looking for a solution w/o reflection. Meaning: During setup/configuration/initialization reflection is allowed, but during the mapping itself, I do not want any delays caused by reflection.
* EDIT *
I am looking for a generic solution, just like the serializers.
There are a lot of possible solutions for your problem.
I've create a custom value resolver for your property and it works perfectly:
public class CustomResolver : IValueResolver<Source, Destination, Dictionary<string, object>>
{
public Dictionary<string, object> Resolve(Source source, Destination destination, Dictionary<string, object> destMember, ResolutionContext context)
{
destMember = new Dictionary<string, object>();
var flags = BindingFlags.Public | BindingFlags.Instance;
var sourceProperties = typeof(Source).GetProperties(flags);
foreach (var property in sourceProperties)
{
if (typeof(Destination).GetProperty(property.Name, flags) == null)
{
destMember.Add(property.Name, property.GetValue(source));
}
}
return destMember;
}
}
How to use it?
static void Main(string[] args)
{
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.D, opt => opt.ResolveUsing<CustomResolver>());
});
var source = new Source { A = 1, B = 2, C = 3 };
var result = Mapper.Map<Source, Destination>(source);
}
public class Source
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
public class Destination
{
public int A { get; set; }
public Dictionary<string, object> D { get; set; }
}
I like Pawel's solution because is more generic.
If you want something simpler but less generic you could initialize the mapper like this:
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.D,
opt => opt.MapFrom(r => new Dictionary<string,object>(){{ "B", r.B},{ "C", r.C}}));
});
You could combine Pawel's answer and Reflection.Emit to make it fast. Be aware that Reflection.Emit is not supported on all platforms (like iOS).
Unlike ExtensionData, this includes all property values from the source. I don't have an elegant solution to determine which properties were already mapped so I just provided an easy way to exclude certain properties.
public class PropertyDictionaryResolver<TSource> : IValueResolver<TSource, object, Dictionary<string, object>>
{
private static readonly PropertyInfo[] Properties = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
private static readonly ConcurrentDictionary<PropertyInfo, Func<TSource, object>> GetterCache = new ConcurrentDictionary<PropertyInfo, Func<TSource, object>>();
public HashSet<MemberInfo> ExcludedProperties;
public PropertyDictionaryResolver()
{
ExcludedProperties = new HashSet<MemberInfo>();
}
public PropertyDictionaryResolver(Expression<Func<TSource, object>> excludeMembers)
{
var members = ExtractMembers(excludeMembers);
ExcludedProperties = new HashSet<MemberInfo>(members);
}
public Dictionary<string, object> Resolve(TSource source, object destination, Dictionary<string, object> existing, ResolutionContext context)
{
var destMember = new Dictionary<string, object>();
foreach (var property in Properties)
{
if (ExcludedProperties.Contains(property)) continue;
var exp = GetOrCreateExpression(property);
var value = exp(source);
if (value != null)
{
destMember.Add(property.Name, value);
}
}
return destMember;
}
/// <summary>
/// Creates and compiles a getter function for a property
/// </summary>
/// <param name="propInfo"></param>
/// <returns></returns>
public Func<TSource, object> GetOrCreateExpression(PropertyInfo propInfo)
{
if (GetterCache.TryGetValue(propInfo, out var existing))
{
return existing;
}
var parameter = Expression.Parameter(typeof(TSource));
var property = Expression.Property(parameter, propInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<TSource, object>>(conversion, parameter);
existing = lambda.Compile();
GetterCache.TryAdd(propInfo, existing);
return existing;
}
/// <summary>
/// Pull the used MemberInfo out of a simple expression. Supports the following expression types only:
/// s => s.Prop1
/// s => new { s.Prop1, s.Prop2 }
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
public static IEnumerable<MemberInfo> ExtractMembers<T>(Expression<Func<T, object>> expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
switch (expression.Body)
{
case MemberExpression memberExpression:
yield return memberExpression.Member;
yield break;
// s => s.BarFromBaseType
case UnaryExpression convertExpression:
if (convertExpression.Operand is MemberExpression exp)
{
yield return exp.Member;
}
yield break;
// s => new { s.Foo, s.Bar }
case NewExpression newExpression:
if (newExpression.Arguments.Count == 0)
{
yield break;
}
foreach (var argument in newExpression.Arguments.OfType<MemberExpression>())
{
yield return argument.Member;
}
yield break;
}
throw new NotImplementedException("Unrecognized lambda expression.");
}
}
And use it like one of these
[TestClass]
public class Examples
{
[TestMethod]
public void AllProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom<PropertyDictionaryResolver<Source>>());
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"A":1,"B":2,"C":3}}
}
[TestMethod]
public void ExcludeSingleProperty()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => x.A)));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"B":2,"C":3}}
}
[TestMethod]
public void ExcludeMultipleProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => new
{
x.A,
x.B
})));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"C":3}}
}
}

Generic way to link to classes/objects

I'd like to write a generic extension method to link two classes/objects
Classes:
public class Model1
{
public Guid Model2ID { get; set; }
public Model2 Model2 { get; set; }
// .. other insignificant properties to this question
}
public class Model2
{
public Guid ID { get; set; }
}
I'm trying to write a method that looks something like:
public static class ObjectExtensions
{
public static Void LinkTo<T,U>(
this T m1,
IEnumerable<U> m2,
m1p // expression to select what property on m1 is populated
Action<bool,T,IEnumerable<U>> expression)
{
// not sure about this part at all
m1p = u2.FirstOrDefault(expression);
}
}
Usage:
var listOfModel2 = //....
Model1.LinkTo(listOfModel2, m => m.Model2, (m1,m2) m1.Model2Id == m2.ID);
As we had discussed in Chat, I'd recommend a light-weight version of EF Context (with reflection). This is completely custom and dynamic, you simply need to use KeyAttribute and ForeignKeyAttribute on your models and add the models to your this custom Context. I only wired up the Add, as the rest you should be able to figure out on your own.
Classes:
public class Staff
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Contact")]
public Guid ContactId { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Dog")]
public Guid DogId { get; set; }
public Dog Dog { get; set; }
}
public class Dog
{
[Key]
public Guid Id { get; set; }
}
Context:
public class Context
{
//Add as many of these as you want. Don't forget to make public properties for them!
private ObservableCollection<Staff> _staffs = new ObservableCollection<Staff>();
private ObservableCollection<Contact> _contacts = new ObservableCollection<Contact>();
private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>();
private List<IForeignKeyRelation> _relations = new List<IForeignKeyRelation>();
public Context()
{
var observables = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.ToList();
foreach(var observable in observables)
{
var notifyCollection = observable.GetValue(this) as INotifyCollectionChanged;
if (notifyCollection != null)
{
notifyCollection.CollectionChanged += CollectionChanged;
Type principalType = observable.FieldType.GetGenericArguments()[0];
var relations = principalType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(p => p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute != null)
.Select(p => new { PrincipalForeignKeyInfo = p, ForeignKey = p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute })
.Where(p => principalType.GetProperty(p.ForeignKey.Name) != null)
.Select(p => {
var principalForeignKeyInfo = p.PrincipalForeignKeyInfo;
var principalRelationInfo = principalType.GetProperty(p.ForeignKey.Name);
var dependantType = principalRelationInfo.PropertyType;
var dependantKeyProperties = dependantType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(dp => dp.GetCustomAttribute(typeof(KeyAttribute)) as KeyAttribute != null)
.ToList();
var dependantKeyInfo = dependantKeyProperties.FirstOrDefault();
var isValid = (dependantKeyInfo != null)
// Don't allow INT to GUID comparisons
// Keys need to be of same type;
&& (principalForeignKeyInfo.PropertyType == dependantKeyInfo.PropertyType);
return new {
IsValid = isValid,
PrincipalRelationInfo = principalRelationInfo,
DependantType = dependantType,
PrincipalCollection = observable.GetValue(this),
PrincipalForeignKeyInfo = principalForeignKeyInfo,
DependantKeyInfo = dependantKeyInfo
};
})
.Where(r => r.IsValid)
.Select(r =>
{
var relationType = typeof(ForeignKeyRelation<,>).MakeGenericType(principalType, r.DependantType);
var relation = Activator.CreateInstance(relationType) as IForeignKeyRelation;
relation.GetType().GetProperty("PrincipalCollection").SetValue(relation, observable.GetValue(this));
relation.DependantKeyInfo = r.DependantKeyInfo;
relation.PrincipalForeignKeyInfo = r.PrincipalForeignKeyInfo;
relation.PrincipalRelationInfo = r.PrincipalRelationInfo;
return relation;
})
.ToList();
_relations.AddRange(relations);
}
}
}
// Makes storing Generic types easy when the
// Generic type doesn't exist;
private interface IForeignKeyRelation
{
PropertyInfo PrincipalForeignKeyInfo { get; set; }
PropertyInfo PrincipalRelationInfo { get; set; }
PropertyInfo DependantKeyInfo { get; set; }
void Add<T>(T value);
}
// Class to hold reflected values
// Reflection
private class ForeignKeyRelation<P,D> : IForeignKeyRelation
{
// Staff.ContactId
public PropertyInfo PrincipalForeignKeyInfo { get; set; }
public Collection<P> PrincipalCollection { get; set; }
// Staff.Contact
public PropertyInfo PrincipalRelationInfo { get; set; }
// Contact.Id
public PropertyInfo DependantKeyInfo { get; set; }
public void Add<T>(T value)
{
if (value.GetType() == typeof(D))
{
var dependantKey = DependantKeyInfo.GetValue(value);
var principals = PrincipalCollection.Where(p => this.PrincipalForeignKeyInfo.GetValue(p).Equals(dependantKey))
.ToList();
foreach(var principal in principals)
{
PrincipalRelationInfo.SetValue(principal, value);
}
}
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach(var relation in this._relations)
{
foreach(var item in args.NewItems)
{
relation.Add(item);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
break;
case NotifyCollectionChangedAction.Replace:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new NotImplementedException(args.Action.ToString());
}
}
public IList<Staff> Staffs
{
get
{
return _staffs;
}
}
public IList<Contact> Contacts
{
get
{
return _contacts;
}
}
public IList<Dog> Dogs
{
get
{
return _dogs;
}
}
}
Simple example program:
public static void Main()
{
var context = new Context();
var staff = new Staff() { Id = Guid.NewGuid() };
var contact = new Contact();
contact.Id = Guid.NewGuid();
contact.Name = "Hello DotFiddle!";
staff.ContactId = contact.Id;
context.Staffs.Add(staff);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
context.Contacts.Add(contact);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
Console.WriteLine("Staff.Contact.Name: "+ staff.Contact.Name);
}
result:
staff contact is null: True
staff contact is null: False
Staff.Contact.Name: Hello DotFiddle!
This Entire Example on DotNetFiddle.net
I don't know exactly what you're going for, but one of these accomplishes it: The bottom two are 'iffy' as you can see by all the if statements. Simply put there's no way for the compiler to be able to be sure that they'll work, since you can easily pass a bad propertyExpression.
class Program
{
static void Main(string[] args)
{
var guids = Enumerable.Range(0, 10).Select(i => Guid.NewGuid()).ToList();
var m2s = guids.Select(g => new Model2 { ID = g }).ToList();
var model1 = new Model1 { Model2ID = m2s[4].ID };
model1.LinkTo(m2s, (m1, m2) => m1.Model2 = m2, (m1, m2) => m2.ID == m1.Model2ID);
var model1a = new Model1 { Model2ID = m2s[4].ID };
model1a.LinkTo(m2s, m1 => m1.Model2, m1 => m1.Model2ID, m2 => m2.ID);
var model1b = new Model1 { Model2ID = m2s[4].ID };
model1b.LinkTo(m2s, m1 => m1.Model2, (m1, m2) => m1.Model2ID == m2.ID);
}
}
public static class ObjectExtensions
{
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Action<T, U> action, Func<T, U, bool> filter)
{
if (m2s.Any(m2 => filter(m1, m2)))
{
var x = m2s.First(m2 => filter(m1, m2));
action(m1, x);
}
}
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, U, bool> filter)
{
var results = m2s.Where(m2 => filter(m1, m2));
if (!results.Any())
return;
var x = results.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
public static void LinkTo<T, U, Key>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, Key> tKey, Func<U, Key> uKey)
{
var results = Enumerable.Repeat(m1, 1)
.Join(m2s, tKey, uKey, (t, u) => u);
if(!results.Any())
return;
var x = results
.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
}
You should read about extension methods. They are called "from" object.
For example you can write generic extension method as below
class Program
{
static void Main(string[] args)
{
var listOfModel2 = new Model1();
//You can call it from "object"
listOfModel2.MyExtensionMethod();
//Or directly as it is declared
ObjectExtensions.MyExtensionMethod(listOfModel2);
}
}
public static class ObjectExtensions
{
public static void MyExtensionMethod<T>(this T t)
{
//Do somthing
}
}

How to deep copy members of a strongly typed collection

I have two classes XmlPerson and Person, each class has public properties, no methods nor any fields.
How would I deep copy all the properties from Person to XmlPerson? I dont want to use a third-party library like MiscUtil.PropertyCopy or Automapper. I have managed to copy the "first-level" properties that are primitive types and strongly typed objects but when it comes the List I have no idea.
The structure of the Person class is below:
public class Person
{
public string FirstName { get; set; }
public string Surname { get; set; }
public decimal? Salary { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
public NextOfKin NextOfKin { get; set; }
}
public class NextOfKin
{
public string FirstName { get; set; }
public string Surname { get; set; }
public string ContactNumber { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
}
public class AddressDetails
{
public int HouseNumber { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
thank u for the help.
charles
Here is the what I have so far:
public class XmlTestCaseToClassMapper
{
internal TTarget MapXmlClassTotargetClass(TSource xmlPerson)
{
var targetObject = Activator.CreateInstance();
var sourceObject = Activator.CreateInstance();
//var xmlClassProperties = xmlPerson.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var xmlClassProperties = GetProperties(xmlPerson.GetType());
//var targetClassProperties = targetObject.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var targetClassProperties = GetProperties(targetObject.GetType());
PropertyInfo targetClassProperty = null;
foreach (var xmlProperty in xmlClassProperties)
{
if (!xmlProperty.PropertyType.IsClass || xmlProperty.PropertyType.UnderlyingSystemType == typeof(string)
|| xmlProperty.PropertyType.IsPrimitive)
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
var propertyValue = xmlProperty.GetValue(xmlPerson, null);
targetClassProperty.SetValue(targetObject, propertyValue, null);
}
else if (xmlProperty.PropertyType.UnderlyingSystemType == typeof(NextOfKin)) //Check subType of the property
{
var subPropertyInstance = Activator.CreateInstance(xmlProperty.GetType());
var subProperties = GetProperties(xmlProperty.GetType());
subProperties.ForEach(subProperty =>
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == subProperty.Name && x.GetType().IsClass);
targetClassProperty.SetValue(subPropertyInstance, xmlProperty.GetValue(this, null), null);
});
}
//else if (xmlProperty.PropertyType.IsGenericType)
//{
// var xmlGenericType = xmlProperty.PropertyType.GetGenericArguments().First();
// var xmlGenericTypeProperties = GetProperties(xmlGenericType);
// targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
// var targetGenericType = targetClassProperty.PropertyType.GetGenericArguments().First();
// var targetGenericProperties = GetProperties(targetGenericType);
// Type targetGenericList = typeof(List<>).MakeGenericType(new Type[] { targetGenericType });
// object listInstance = Activator.CreateInstance(targetGenericList);
// //foreach (var xmlGenericProperty in xmlGenericTypeProperties)
// //{
// // var targetGenericProperty = targetGenericProperties.FirstOrDefault(x => x.Name == xmlGenericProperty.Name);
// // targetGenericProperty.SetValue(targetGenericProperty, xmlGenericProperty.GetValue(xmlGenericType, null), null);
// //}
// xmlGenericTypeProperties.ForEach(x =>
// {
// foreach (var targetGenericProperty in targetGenericProperties)
// {
// targetGenericProperty.SetValue(targetGenericProperty, targetGenericProperty.GetValue(x, null), null);
// }
// });
//}
//}
}
return targetObject;
}
private List<PropertyInfo> GetProperties(Type targetType)
{
var properties = new List<PropertyInfo>();
targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList().ForEach(property =>
{
properties.Add(property);
});
return properties.OrderBy(x => x.Name).ToList();
}
}
Here is a possible solution. It probably requires some tweaks based on your actual classes.
public T DeepCopy<S, T>(S source) where T : new()
{
var sourceProperties = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public);
T target = new T();
foreach (var sourceProperty in sourceProperties)
{
var property = typeof(T).GetProperty(sourceProperty.Name);
if (property.PropertyType.IsPrimitive ||
property.PropertyType == typeof(string) ||
(property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
object value = sourceProperty.GetValue(source);
property.SetValue(target, value);
}
else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
var sourceList = (IEnumerable)sourceProperty.GetValue(source);
if (sourceList != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType.GenericTypeArguments[0], property.PropertyType.GenericTypeArguments[0]);
var ctor = property.PropertyType.GetConstructor(Type.EmptyTypes);
IList targetList = (IList) ctor.Invoke(null);
foreach (var element in sourceList)
{
targetList.Add(deepCopy.Invoke(this, new object[] { element } ));
}
property.SetValue(target, targetList);
}
}
else
{
var value = sourceProperty.GetValue(source);
if (value != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType, property.PropertyType);
property.SetValue(target, deepCopy.Invoke(this, new object[] { value }));
}
}
}
return target;
}

Categories