потеря наследуемых типов на пути от ядра asp.net к Angular

Я разрабатываю приложение с asp.net core 2.2 и ef core 2.2 на сервере и Angular 7 на стороне клиента. Я не могу понять это:

У меня есть следующие (упрощенные) модели:

public abstract class LegalEntity
{
    public int Id { get; set; }
    public int Name { get; set; }
    public Address FiscalAddress { get; set; }
}

public class Organisation : LegalEntity
{
    public Organisation(){}
    public Organisation(LegalEntityType legalEntityType, string name, Address fiscalAddress)
    {
        LegalEntityType = legalEntityType;
        Name = name;
    }
    public LegalEntityType LegalEntityType { get; set; }
}


public class LegalEntityType
{
    public int Id { get; set; }
    public int Name { get; set; }
    public LegalEntityType(){}
    public LegalEntityType(string name)
    {
        Name = name;
    }
}


public class Person : LegalEntity
{
    public Gender Gender { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override string Name => string.Format("{0} {1}", FirstName, LastName);
}

public class Gender
{
    public int Id { get; set; }
    public int Name { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public Customer(){}
    public Customer(LegalEntity legalEntity)
    {
        LegalEntity = legalEntity;
    }
    public LegalEntity LegalEntity { get; set; }
}

Когда я возвращаю сущности клиента клиенту через API, иногда LegalEntity является организацией, а иногда — лицом. После того, какой тип (Организация или Лицо) возвращается, я хочу, чтобы свойство LegalEntityType (в случае организации) или свойство Пол (в случае лица) отображалось в коде JSON. Это моя первая проблема, следующее оставляет их обоих нулевыми:

.Include(o => o.Customer).ThenInclude(o => o.LegalEntity)

так как это не загружает свойства навигации, которые присутствуют только в наследующих сущностях. Вот выдержка из строки JSON в случае

Человек:

...
 "customer": {
            "legalEntity": {
                "gender": null,
                "firstName": "Denis",
                "lastName": "Testmann",
                "name": "Denis Testmann",
                "id": 9
            },
...

Организация:

...
 "customer": {
            "legalEntity": {
                "legalEntityType": null,
                "name": "Companyname GmbH",
                "id": 6
            },
...

Вместо этого должно выйти следующее:

Человек:

...
 "customer": {
            "Person": {
                "gender": null,
                "firstName": "Denis",
                "lastName": "Testmann",
                "name": "Denis Testmann",
                "id": 9
            },
...

Организация:

...
 "customer": {
            "Organisation": {
                "legalEntityType": null,
                "name": "Companyname GmbH",
                "id": 6
            },
...

Чтобы подчеркнуть это: клиент может быть физическим лицом или организацией, обе Сущности (Организация и Лицо) наследуются от LegalEntity, и поэтому свойство клиентов «Юридическое лицо» иногда является Лицом, иногда Организацией. Когда я визуализирую JSON, необходимо поддерживать определенный тип.

Надеюсь, я был достаточно ясен - пожалуйста, извините меня так долго, я хочу быть уверен, что проблема понята.


person Fabianus    schedule 23.01.2019    source источник


Ответы (2)


Новый ответ

Я собрал следующие Program.cs через dotnet new console через dotnet core 2.1:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

class Program
{
    static void Main(string[] args)
    {
        var list = new List<Customer>();
        list.Add(new Customer(new Person { Gender = new Gender{ Name = "Male"}}));
        list.Add(new Customer(new Organisation { LegalEntityType = new LegalEntityType{ Name = "GmbH"}}));
        Console.WriteLine(JsonConvert.SerializeObject(list, Newtonsoft.Json.Formatting.Indented));
    }
}

public abstract class LegalEntity
{
   public int Id { get; set; }
   public virtual string Name { get; set; }
   public Address FiscalAddress { get; set; }
}

public class Organisation : LegalEntity
{
   public Organisation(){}
   public Organisation(LegalEntityType legalEntityType, string name, Address fiscalAddress)
   {
       LegalEntityType = legalEntityType;
       Name = name;
   }
   public LegalEntityType LegalEntityType { get; set; }
}


public class LegalEntityType
{
   public int Id { get; set; }
   public string Name { get; set; }
   public LegalEntityType(){}
   public LegalEntityType(string name)
   {
       Name = name;
   }
}

public class Person : LegalEntity
{
   public Gender Gender { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public override string Name => string.Format("{0} {1}", FirstName, LastName);
}

public class Gender
{
   public int Id { get; set; }
   public string Name { get; set; }
}


public class Customer
{
   public int Id { get; set; }
   public Customer(){}
   public Customer(LegalEntity legalEntity)
   {
       LegalEntity = legalEntity;
   }
   public LegalEntity LegalEntity { get; set; }
}    

public class Address
{
   public int Id { get; set; }
   public string Line1 { get; set; }
   public string Line2 { get; set; }   
}

Вывод выглядит следующим образом:

[
  {
    "Id": 0,
    "LegalEntity": {
      "Gender": {
        "Id": 0,
        "Name": "Male"
      },
      "FirstName": null,
      "LastName": null,
      "Name": " ",
      "Id": 0,
      "FiscalAddress": null
    }
  },
  {
    "Id": 0,
    "LegalEntity": {
      "LegalEntityType": {
        "Id": 0,
        "Name": "GmbH"
      },
      "Id": 0,
      "Name": null,
      "FiscalAddress": null
    }
  }
]

Это выглядит нормально? Возможно, проверьте, какие настройки сериализатора использует ваш связыватель конечных точек API.

Старый ответ

Если LegalEntity является реальной таблицей в базе данных, вы сможете использовать:

    [ForeignKey("LegalEntity")]
    public int LegalEntityId { get; set; }

в вашем Customer определении.

Вам также нужно установить ключ:

    public abstract class LegalEntity
    {
        [Key]
        public int Id { get; set; }
        public int Name { get; set; }
        public Address FiscalAddress { get; set; }
    }

если LegalEntity не является реальной таблицей, добавьте отдельные переходы для человека/организации

person user326608    schedule 23.01.2019
comment
Привет @ user326608 - спасибо за ответ. Да, LegalEntity - это реальная таблица (и используется организацией и лицом), но на самом деле нагрузка из базы данных не является моей проблемой - это работает нормально (поскольку я придерживаюсь соглашения, мне не нужно использовать атрибут ForeignKey). Моя проблема заключается в переходе в JSON, где теряются типы Person и Organization. Может быть, я недостаточно ясно выразился в своем вопросе? - person Fabianus; 23.01.2019
comment
Итак, когда вы отлаживаете, вы можете видеть связанные объекты, но сериализатор json не чередует их, как ожидалось? посмотреть, поможет ли stackoverflow.com/a/38529712? похоже, вам может потребоваться явно указать Json.NET - person user326608; 23.01.2019
comment
да и нет, сериализатор разделяет организацию и сущность-лицо как LegalEntity - и вы, вероятно, ожидаете этого в некотором роде, поскольку они оба наследуются от LegalEntity. Я хочу, чтобы не потерять фактический конкретный тип (который является личным лицом или организацией), поскольку LegalEntity является только абстрактным. Я просто добавил несколько пояснений во второй половине моего вопроса, чтобы сделать его более понятным - надеюсь, это поможет понять мою проблему. Большое спасибо за вашу поддержку. - person Fabianus; 23.01.2019
comment
см. мой обновленный ответ, посмотрите, можете ли вы использовать пример программы для воспроизведения вашей проблемы. я пытался использовать var legalEntities = list.Select(x => x.LegalEntity).ToList<LegalEntity>(); и все равно получил тот же вывод json - person user326608; 24.01.2019
comment
Привет ! Я могу воспроизвести вывод. Интересно, есть ли у меня неправильное представление о том, как наследуемые типы передаются в JSON. В вашем выводе обе Сущности (Организация и Лицо) также хранятся в подсхеме LegalEntity. Если я хочу их десериализовать (в моем случае в Angular, но может быть и в C#), как десериализатор узнает, является ли LegalEntity организацией или лицом? Я предполагаю, что лучше всего было бы, чтобы сериализатор называл подсхему либо «Организация», либо «Лицо» вместо «Юридическая сущность» в обоих случаях. Спасибо, если бы вы могли разъяснить мне это! - person Fabianus; 29.01.2019

ок - решение найдено!

на стороне сервера вам необходимо сериализовать содержимое JSON с помощью

SerializerSettings.TypeNameHandling = TypeNameHandling.Auto

что вы должны сделать в файле Startup.cs:

    services.AddJsonOptions(opt =>
    {
        opt.SerializerSettings.ReferenceLoopHandling =
            Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        opt.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
    })

а на стороне клиента вы можете использовать класс-трансформер (https://github.com/typestack/class-transformer).

для моего примера класс клиента, где LegalEntity может быть типа Person или Organization, выглядит следующим образом:

public class Customer
   {
       public int Id { get; set; }

    @Type( () => Object, {
        discriminator: {
          property: '$type',
          subTypes: [
            {value: Person, name: 'Your.Name.Space.Person, YourApp.Name'},
            {value: Organisation, name: 'Your.Name.Space.Organisation, YourApp.Name'},
          ],
        },
      })
       public LegalEntity LegalEntity { get; set; }
   }

а затем вы создаете экземпляр класса клиента из простого объекта javascript, например:

    import { plainToClass } from 'class-transformer';

    export class SomeClassInYourAngularApp implements OnInit {

customerList: Customer[];

      ngOnInit() {
        this.customerList = new Array<Customer>();
        let u: any;

    // lets get the plain js object form somewhere, ex. from a resolver
        this.route.data.subscribe(data => {
          u = data['customerList'].result;
        });

        u = plainToClass(Customer, u);
        Object.assign(this.customerList, u);
      }

Это оно !

person Fabianus    schedule 27.02.2019