c# - 我可以使用 AutoMapper 映射目标继承而不需要源继承吗?

标签 c# entity-framework inheritance automapper entity-framework-core

我搜索了很多次,但无法弄清楚是否有办法使用 AutoMapper (5.2) 来映射以下 EF Core (1.1) 场景。源类不使用继承,因为尚不支持 Table-per-Type 继承,而且我正在使用现有数据库。

EF Core 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 ,而不必为每个 DTO 写出来继承自FarmerChangeDtoBase

最佳答案

与其尝试在没有源继承的情况下映射目标继承,不如解决问题的根源 - 缺乏源继承。

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

例如,示例模型为 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);
    });

如果我们提取一个具有公共(public)属性的基类,并让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));
}

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

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"));
}

关于c# - 我可以使用 AutoMapper 映射目标继承而不需要源继承吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41304430/

相关文章:

c# - 在 Visual Studio 2015 中滚动智能感知列表的键绑定(bind)是什么?

sql - 为什么 LINQ to Entities 为我创建子查询?

c++ - 为什么 x 成员变量不明确?

c# - 如何在 C# 中创建涉及集合的单元测试?

c# - 如何在 UWP 的 Pivot 中获取 Pivot.ItemTemplate 中的控件?

entity-framework - 我可以一次性忽略 Automapper 属性吗?

entity-framework - Entity Framework Linq 将 lambda 表达式嵌入到可重用函数中

java - 是否可以在子类中弃用父类(super class)的方法?

php - 抽象父级使用子级别名

c# - 使用 Autofac 的 ASP.NET Core v2.0 的 SignalR 依赖注入(inject)