我已经搜索过很多东西,无法确定是否有办法使用AutoMapper(5.2)映射以下EF Core(1.1)方案。源类不使用继承,因为尚不支持Table-per-Type继承,并且我正在使用现有数据库。

EF核心POCOS:

public class Farmer
{
    [Key]
    public int FarmerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    //changed entities
    public virtual ICollection<Chicken> ChangedChickens { get; set; }
    public virtual ICollection<Cow> ChangedCows { get; set; }
}

public class Chicken
{
    [Key]
    public int ChickenId { get; set; }
    public bool IsRooster { get; set; }
    //common change props
    public int LastChangeByFarmerId { get; set; }
    public DateTime LastChangeTimestamp { get; set; }
    [ForeignKey("LastChangeBy")]
    public virtual Farmer LastChangeFarmer { get; set; }
}

public class Cow
{
    [Key]
    public int CowId { get; set; }
    public string Name { get; set; }
    //common change props
    public int LastChangeByFarmerId { get; set; }
    public DateTime LastChangeTimestamp { get; set; }
    [ForeignKey("LastChangeBy")]
    public virtual Farmer LastChangeFarmer { get; set; }
}


我想对数据传输类中的更改属性使用基类:

DTO

public abstract class FarmerChangeDtoBase
{
    public int LastChangeBy { get; set; }
    public DateTime LastChangeTime { get; set; }
    public string ChangingFarmerFirstName { get; set; }
    public string ChangingFarmerLastName { get; set; }
    public string ChangingFarmerFullName => $"{ChangingFarmerFirstName} {ChangingFarmerLastName}";
}

public class ChickenDto : FarmerChangeDtoBase
{
    public int ChickenId { get; set; }
    public bool IsRooster { get; set; }
}

public class CowDto : FarmerChangeDtoBase
{
    public int CowId { get; set; }
    public string Name { get; set; }
}


我写了一个扩展方法来使用反射来获取LastChangeByLastChangeTime值,这可能不是理想的选择,但是我无法弄清楚如何获取农夫名称的嵌套属性。这是扩展方法:

public static IMappingExpression<TSource, TDest> MapChangeFarmer<TSource, TDest>(
    this IMappingExpression<TSource, TDest> mappingExpression)
    where TDest : FarmerChangeDtoBase
{
    return mappingExpression.ForMember(d => d.LastChangeBy,
            opt => opt.MapFrom(s =>
                (int) s.GetType().GetProperty("LastChangeByFarmerId").GetValue(s)))
         .ForMember(d => d.LastChangeTime,
            opt => opt.MapFrom(s =>
                (DateTime) s.GetType().GetProperty("LastChangeTimestamp").GetValue(s)));
    //what/how can I map the name properties???
}


有什么方法可以在扩展方法中映射嵌套的属性LastChangeFarmer.FirstNameLastChangeFarmer.LastName,而不必为继承自FarmerChangeDtoBase的每个DTO都将其写出?

最佳答案

而不是尝试在没有源继承的情况下映射目标继承,让我们解决问题的根源-缺少源继承。

EF(核心)继承是围绕单个多态抽象实体集对实体继承建模并将其映射到单个表(TPH)或多个表(TPT或TPC)的概念。但是,EF(核心)不禁止在不使用EF继承的情况下使用POCO类继承-使用像您这样的包含公共实体属性的基类绝对没有问题。

例如,样本模型为ChickenCow实体生成以下表:

migrationBuilder.CreateTable(
    name: "Chicken",
    columns: table => new
    {
        ChickenId = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        IsRooster = table.Column<bool>(nullable: false),
        LastChangeByFarmerId = table.Column<int>(nullable: false),
        LastChangeTimestamp = table.Column<DateTime>(nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Chicken", x => x.ChickenId);
        table.ForeignKey(
            name: "FK_Chicken_Farmer_LastChangeByFarmerId",
            column: x => x.LastChangeByFarmerId,
            principalTable: "Farmer",
            principalColumn: "FarmerId",
            onDelete: ReferentialAction.Cascade);
    });

migrationBuilder.CreateTable(
    name: "Cow",
    columns: table => new
    {
        CowId = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        LastChangeByFarmerId = table.Column<int>(nullable: false),
        LastChangeTimestamp = table.Column<DateTime>(nullable: false),
        Name = table.Column<string>(nullable: true)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Cow", x => x.CowId);
        table.ForeignKey(
            name: "FK_Cow_Farmer_LastChangeByFarmerId",
            column: x => x.LastChangeByFarmerId,
            principalTable: "Farmer",
            principalColumn: "FarmerId",
            onDelete: ReferentialAction.Cascade);
    });


如果我们提取具有公共属性的基类,并让ChickenCow从其继承:

public abstract class FarmerChangeBase
{
    public int LastChangeByFarmerId { get; set; }
    public DateTime LastChangeTimestamp { get; set; }
    [ForeignKey("LastChangeByFarmerId")]
    public virtual Farmer LastChangeFarmer { get; set; }
}

public class Chicken : FarmerChangeBase
{
    [Key]
    public int ChickenId { get; set; }
    public bool IsRooster { get; set; }
}

public class Cow : FarmerChangeBase
{
    [Key]
    public int CowId { get; set; }
    public string Name { get; set; }
}


在不使用继承的情况下,最终的迁移(因此映射)与上一个完全相同。

一旦这样做,由于适当的通用类型约束,映射方法很简单:

public static IMappingExpression<TSource, TDest> MapChangeFarmer<TSource, TDest>(
    this IMappingExpression<TSource, TDest> mappingExpression)
    where TSource : FarmerChangeBase
    where TDest : FarmerChangeDtoBase
{
    return mappingExpression
        .ForMember(d => d.LastChangeBy, opt => opt.MapFrom(s => s.LastChangeByFarmerId))
        .ForMember(d => d.ChangingFarmerFirstName, opt => opt.MapFrom(s => s.LastChangeFarmer.FirstName))
        .ForMember(d => d.ChangingFarmerLastName, opt => opt.MapFrom(s => s.LastChangeFarmer.LastName))
        .ForMember(d => d.LastChangeTime, opt => opt.MapFrom(s => s.LastChangeTimestamp));
}


附言为了回答最初的问题,如果由于某种原因您不能使用源继承,以下自定义扩展方法将通过包含属性路径的字符串动态构建所需的表达式:

public static void MapFromPath<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, string memberPath)
{
    var source = Expression.Parameter(typeof(TSource), "s");
    var member = memberPath.Split('.').Aggregate(
        (Expression)source, Expression.PropertyOrField);
    var selector = Expression.Lambda<Func<TSource, TMember>>(member, source);
    opt.MapFrom(selector);
}


可以这样使用:

public static IMappingExpression<TSource, TDest> MapChangeFarmer<TSource, TDest>(
    this IMappingExpression<TSource, TDest> mappingExpression)
    where TDest : FarmerChangeDtoBase
{
    return mappingExpression
        .ForMember(d => d.LastChangeBy, opt => opt.MapFromPath("LastChangeByFarmerId"))
        .ForMember(d => d.ChangingFarmerFirstName, opt => opt.MapFromPath("LastChangeFarmer.FirstName"))
        .ForMember(d => d.ChangingFarmerLastName, opt => opt.MapFromPath("LastChangeFarmer.LastName"))
        .ForMember(d => d.LastChangeTime, opt => opt.MapFromPath("LastChangeTimestamp"));
}

09-10 04:29
查看更多