AutoMapperを使用して、ソース継承なしで宛先継承をマップできますか?

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

質問

私は高低を検索しており、AutoMapper(5.2)を使用して次のEF Core(1.1)シナリオをマッピングする方法があるかどうかはわかりません。ソースクラスは、テーブルごとの継承がまだサポートされておらず、既存のデータベースに対して作業しているため、継承を使用しません。

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???
}

FarmerChangeDtoBaseから継承するすべての単一のDTOに対して書き込むのではなく、ネストされたプロパティLastChangeFarmer.FirstNameおよびLastChangeFarmer.LastNameを拡張メソッドにマップする方法はありますか?

受け入れられた回答

ソース継承を持たずにデスティネーション継承をマップするのではなく、ソース継承の欠如という問題の原因を解決してください。

EF(Core)継承は、 単一の多態抽象エンティティセットを囲むエンティティの継承をモデル化し、単一のテーブル(TPH)または複数のテーブル(TPTまたはTPC)にマッピングする概念です。しかし、EF(Core)は、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));
}

PS元の質問に答えるために、何らかの理由でソース継承を使用できない場合、次のカスタム拡張メソッドは、プロパティパスを含む文字列から必要な式を動的に構築します。

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


Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ