2

Я искал высоко и низко и не могу понять, есть ли способ использовать AutoMapper (5.2) для сопоставления следующего сценария EF Core (1.1). Исходные классы не используют наследование, поскольку наследование типа «на каждый тип» еще не поддерживается, и я работаю против существующей базы данных.Могу ли я использовать AutoMapper для наследования назначения наследования без наследования источника?

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

Я хотел бы использовать базовый класс для свойств изменения в моих классах передачи данных:

DTOs

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

I написал метод расширения для получения LastChangeBy и. 10 значений с использованием отражения, что может быть не идеальным, но я не могу понять, как получить вложенные свойства для имени фермера. Вот метод расширения:

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.FirstName и LastChangeFarmer.LastName в методе расширения, вместо того, чтобы написать ее для каждого DTO наследуемого от FarmerChangeDtoBase?

+0

Может быть примечание стороны, но я не вижу, что мешает вам иметь базовый класс POCO проведения общих свойств - это не имеет ничего общего с наследованием EF, если ваш 'DbContext' просто включает унаследованный юридические лица. –

+0

@IvanStoev - «По соглашению наследование будет отображаться с использованием шаблона таблицы за иерархию (TPH). TPH использует одну таблицу для хранения данных для всех типов в иерархии. Столбец дискриминатора используется для определения того, какой тип строка представляет. " от https://docs.microsoft.com/en-us/ef/core/modeling/relational/inheritance –

+0

Это когда у вас есть 'DbSet ' в вашем контексте. Когда у вас есть отдельные 'DbSet ' и 'DbSet ', все просто отлично (например, если наследование отсутствует). –

ответ

1

Вместо того, чтобы пытаться отобразить наследование назначения без наследования источника, разрешите возникновение проблемы - отсутствие наследования источника.

EF (основное) наследование является понятием моделирования наследства сущностей вокруг одной полиморфной абстрактной сущности установить и отображение его на одну таблицу (ТРН) или несколько таблиц (TPT или TPC). Однако EF (Core) не запрещает использование наследования класса POCO без использования наследования EF - нет абсолютно никакой проблемы с использованием базовых классов, содержащих общие свойства сущностей, например, в вашем случае.

Например, модель образца производит следующие таблицы для Chicken и Cow сущности:

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

Если извлечь базовый класс с общими свойствами, и пусть Chicken и Cow наследуют от него:

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