7

In my application multiple reports are needed on some table, many of the fields are common in most reports, as a sample:

public class ReportStudent
{
    public int Id {get; set;}
    public string Name {get; set;}
    public string Family {get; set;}
    public DateTime BirthDate {get; set;}
    public DateTime RegisterDate {get; set;}
    public Double Average {get; set;}
    public string FatherName {get; set;}
    public string MotherName {get; set;}
}

var list1 = context.Students.Select(e=> new ReportStudent
{
    Id = e.Id
    Name = e.Name
    Family = e.Family
    BirthDate = e.BirthDate
    RegisterDate = e.RegisterDate
    FatherName = e.FatherName
    MotherName = e.MotherName
}).ToList();

var list2 = context.Students.Select(e=> new ReportStudent
{
    Id = e.Id
    Name = e.Name
    Family = e.Family
    BirthDate = e.BirthDate
    RegisterDate = e.RegisterDate
    Average = e.Average
}).ToList();

How can I write this map only once? These fields are common in list1 and list2.

Id = e.Id
Name = e.Name
Family = e.Family
BirthDate = e.BirthDate
RegisterDate = e.RegisterDate
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Mohammad Akbari
  • 4,486
  • 6
  • 43
  • 74

3 Answers3

7

First, define an expression that will contain your common projection needs:

Expression<Func<ReportStudent, ReportStudent>> commonProjection = e => new ReportStudent
{
    Id = e.Id,
    Name = e.Name,
    Family = e.Family,
    BirthDate = e.BirthDate,
    RegisterDate = e.RegisterDate,
};

Then have a method that will modify this expression to reflect the additional bindings:

public static Expression<Func<ReportStudent, ReportStudent>> MergeBindings(Expression<Func<ReportStudent, ReportStudent>> expr, Expression<Func<ReportStudent, ReportStudent>> newExpr)
{
    var reportStudentType = typeof(ReportStudent);
    var eParameter = expr.Parameters.First();
    var eNew = Expression.New(reportStudentType);

    var memberInitExpr = expr.Body as MemberInitExpression;
    var memberInitNewExpr = newExpr.Body as MemberInitExpression;
    var allBindings = memberInitExpr.Bindings.Concat(memberInitNewExpr.Bindings.Select(x =>
        Expression.Bind(x.Member, Expression.Property(eParameter, x.Member as PropertyInfo)
    )));

    var eInit = Expression.MemberInit(eNew, allBindings);
    var lambda = Expression.Lambda<Func<ReportStudent, ReportStudent>>(eInit, eParameter);

    return lambda;
}

Usage:

var withParentsExpr = MergeBindings(commonProjection, e => new ReportStudent
{
    FatherName = e.FatherName,
    MotherName = e.MotherName
});

var list1 = context.Students.Select(withParentsExpr).ToList();

var withAverageExpr = MergeBindings(commonProjection, e => new ReportStudent
{
    Average = e.Average
});

var list2 = context.Students.Select(withAverageExpr).ToList();

(With some help from @Nicholas Butler great answer)

Community
  • 1
  • 1
haim770
  • 48,394
  • 7
  • 105
  • 133
0

If you don't want to write maps every time, you can use great library http://automapper.org/ With this library, you can define map and it automatically map all properties

Victor Leontyev
  • 8,488
  • 2
  • 16
  • 36
0

You could create a function for that let say you have

public StudentReport ParseStudentReport(Student e)
{
    return new StutentReport{
         Id = e.Id
         Name = e.Name
         Family = e.Family
         BirthDate = e.BirthDate
         RegisterDate = e.RegisterDate
    }
 }

Then use it within your select statement

var list2 = context.Students.Select(ParseStudentReport);

Then add remaining properties or you could use AutoMapper, which can be found on github or as a nuget package.