I want to use AutoMapper's queryable extensions (.ProjectTo
), but I can't figure out how to do it without copying the code in many of my properties on the database objects and duplicating it in the projection. Here's an example that both overrides ToString and has a custom property:
class Claim {
public int Id { get; set; }
public int TypeId { get; set; }
public ClaimType Type { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name
=> $"{FirstName} {LastName}";
}
class ClaimType {
public int Id { get; set; }
public string Value { get; set; }
public string Abbrev { get; set; }
public override string ToString()
=> Value + (Abbrev != null ? $" ({Abbrev})" : "");
}
class ClaimViewModel {
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
}
Now let's say I have the above two database models and view model I want to project the Claim to a ClaimViewModel in a query. The only way I know how to do this is like so:
CreateMap<Claim, ClaimViewModel>()
.ForMember(c => c.ClaimType, x => x.MapFrom(c => c.ClaimType.Value + (c.ClaimType.Abbrev != null ? $" ({c.ClaimType.Abbrev})" : "")))
.ForMember(c => c.Name, x => x.MapFrom(c => $"{c.FirstName} {c.LastName}"));
Obviously this duplicates the code for the Name property and the ClaimType.ToString method. Are there any established patterns for solving this problem and keeping the code DRY? We have lots of custom properties and ToString overrides in our models. I have discovered that I can do the following for the Name property... first, in the Claim class:
public static readonly Expression<Func<Claim, string>> NameExpr = (c) => $"{c.FirstName} {c.LastName}";
public string Name => NameExpr(this);
And then in the mapping do this:
.ForMember(c => c.Name, x => x.MapFrom(Claim.NameExpr));
This seems fine for that very simple case, but it doesn't work for situations with foreign key references, like the ClaimType.ToString method. In that case I could put the expression in Claim and reference it in the Claim mapping, but the moment I needed to project a ClaimType to a ClaimTypeViewModel, I'd have to duplicate the code. Or if there was another database model that referenced the ClaimType model, I'd have the same problem.
If it matters to the answer, the ORM is EF Core @ 2.1.3.
EDIT: I didn't realized it when I wrote this question, but the above mapping will work without the ForMember configuration, but I noticed in SQL Profiler that the query will pull back every column in the Claim and ClaimType models rather than just the columns needed. That's nice, but I really need the performance bonus of ProjectTo only pulling the columns actually needed.