Converting parent to child
I've got tens of domain objects (User, Group, Role, Community, Post etc). Also i've got extended objects (UserExt, GroupExt etc) which are derived from those and contain some addition data. In my data access control layer there are methods for retrieving base objects. When i need to populate child objects with data I use those methods but every time i need to convert that result to the child type.
As I cannot cast parent object as child, i need to provide converters for every parent-child pair (via constructors, methods, extensions to existing converters or any other way). That is what i dont like as if i ever add any field to base type i could forget to adjust my converters. Is there more automatized way to populate child's fields from parent? Thank you!PS: code:
Domain objects:
public class Role : OutdoorObject
{
public String Name { get; set; }
public Int32 CreatedById { get; set; }
public Int32 UpdatedById { get; set; }
}
public class RoleExt : Role
{
public IPrincipal CreatedBy { get; set; }
public IPrincipal UpdatedBy { get; set; }
}
Data access开发者_C百科 layer:
public Role GetById(Int32 roleId)
{
try
{
// seek in cache, return if found
LQ_Role lqRole = context.LQ_Roles.FirstOrDefault(r => r.RoleID == roleId);
Role result = LQMapper.LQToObject(lqRole);
// put result to cache
return result;
}
catch (Exception ex)
{
if (ex is BaseRepositoryException) throw ex;
else throw new UnknownRepositoryException(ex.Message);
}
}
Service layer:
public Role GetById(IPrincipal Executer, int roleID)
{
try
{
// perform operation
Role r = _repo.GetById(roleID);
// check access
if (!CanRead(Executer, r)) throw new OperationIsNotPermittedServiceException();
return r;
}
catch (Exception ex)
{
// ...
}
}
public RoleExt GetExtById(IPrincipal Executer, int roleID)
{
try
{
// perform operation
Role r = GetById(IPrincipal Executer, int roleID);
RoleExt result = new RoleExt();
// here i need to convert r to result
// and populate addition fields
result.CreatedBy = userService.GetById(Executer, r.CreatedById);
result.UpdatedBy = userService.GetById(Executer, r.UpdatedById);
// check access
if (!CanRead(Executer, result)) throw new OperationIsNotPermittedServiceException();
return result;
}
catch (Exception ex)
{
//...
}
}
Use reflection, this will copy all public properties from the parent to the child:
public static void CopyOver(Parent p, Child c)
{
PropertyInfo[] props = p.GetType().GetProperties(BindingFlags.Public);
foreach( PropertyInfo pi in props)
{
pi.SetValue( c, pi.GetValue( p) );
}
}
Small tweak to the example above to an extension method...
/// <summary>
/// Performs a shallow convert from the parent to the child object.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parent">The parent.</param>
/// <param name="child">The child.</param>
public static void ShallowConvert<T, U>(this T parent, U child)
{
foreach (PropertyInfo property in parent.GetType().GetProperties())
{
if (property.CanWrite)
{
property.SetValue(child, property.GetValue(parent, null), null);
}
}
}
AutoMapper is the best way to convert entities. Definitely try it.
Btw, I extended @thumbmunkeys code, if you don't want to use Automapper.
/// <summary>
/// Encapsulates useful functions to map domain objects with presentation objects.
/// </summary>
public static class Mapper
{
/// <summary>
/// Converts an object of <typeparamref name="U"/> to an object of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="U"></typeparam>
/// <typeparam name="T"></typeparam>
/// <param name="instanceOfU"></param>
/// <returns></returns>
public static T Convert<U, T>(U instanceOfU)
where T : class, new()
where U : class, new()
{
T instanceOfT = new T();
PropertyInfo[] tPropertyInfos = typeof(T).GetProperties();
PropertyInfo[] uPropertyInfos = typeof(U).GetProperties();
foreach (PropertyInfo tPropertyInfo in tPropertyInfos)
{
foreach (var uPropertyInfo in uPropertyInfos.Where(p => p.Name == tPropertyInfo.Name))
{
if (tPropertyInfo.PropertyType == uPropertyInfo.PropertyType
&& tPropertyInfo.SetMethod != null)
{
tPropertyInfo.SetValue(instanceOfT, uPropertyInfo.GetValue(instanceOfU));
}
}
}
return instanceOfT;
}
/// <summary>
/// Converts an instance of type <typeparamref name="TChaild"/> to an instance of its parent type: <typeparamref name="TParent"/>.
/// </summary>
/// <typeparam name="TChild"></typeparam>
/// <typeparam name="TParent"></typeparam>
/// <param name="child"></param>
/// <returns></returns>
public static TParent ConvertChildToParent<TChild, TParent>(TChild child)
where TParent : class, new()
where TChild : class, new()
{
if (!typeof(TChild).IsDerivedFrom(typeof(TParent)))
{
throw new ArgumentException(string.Format("{0} is not derived from {1}.", typeof(TChild), typeof(TParent)), "TChild");
}
return Convert<TChild, TParent>(child);
}
/// <summary>
/// Check if this type is derived from <typeparamref name="parentType"/>.
/// </summary>
/// <param name="thisType"></param>
/// <param name="parentType"></param>
/// <returns></returns>
public static bool IsDerivedFrom(this Type thisType, Type parentType)
{
Type derivedType = thisType;
do
{
derivedType = derivedType.BaseType;
if (derivedType != null)
{
if (derivedType == parentType) { return true; }
}
} while (derivedType != null);
return false;
}
}
If you want an easy knowledge to adapt to all of your object, you can use an extension method that use reflection.
Here is a good example
public static class Extension
{
public static object ToChild<TChild, TParent>(this TParent _oParent) where TChild : TParent, new()
{
PropertyInfo[] _apProperties = typeof(TParent).GetProperties();
TParent _oChild = new TChild();
foreach (PropertyInfo _piProperty in _apProperties)
{
_piProperty.SetValue(_oChild, _piProperty.GetValue(_oParent));
}
return (TChild)_oChild;
}
}
In my data access control layer there are methods for retrieving base objects
If in your data model you operate and retrive User
, Group
, Role
, Community
, Post
data types, there is no other way then "manually" implement a "translation" between base object and destination derived class.
There are plenty ways to do that:
- special conversion methods
- overriden cast oprators
- object mapper frameworks
and more...
Just move the convert to the Parent object. This lets you write one convert per Parent, and keep property changes to a single source file.
public class Parent
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public DateTime Prop3 { get; set; }
public T ToChild<T>() where T : Parent, new()
{
T child = new T();
child.Prop1 = this.Prop1;
child.Prop2 = this.Prop2;
child.Prop3 = this.Prop3;
return child;
}
}
精彩评论