Включить только одно свойство, а не всю строку базы данных

#c# #asp.net #entity-framework #asp.net-core #entity-framework-core

#c# #asp.net #entity-framework #asp.net-core #entity-framework-core

Вопрос:

Модель:

 public class Word
{
    public int ID { get; set; }
    public string Title { get; set; }
    public DateTime? WhenCreated { get; set; }
    public ApplicationUser Author { get; set; }

    [NotMapped]
    public string AuthorName
    {
        get
        {
            if (Author != null)
            {
                return Author.UserName;
            }
            else {
                return "";
            }
        }
    }

    public List<Definition> Definitions { get; set; }
}
  

Контроллер:

 [HttpGet]
    public IEnumerable<Word> Get()
    {
        return _db.Words.Include(x=>x.Author).ToList();
    }
  

Мой контроллер теперь возвращает весь ApplicationUser класс, который является одним из свойств Word . Я хочу отправить только одно свойство ApplicationUser : UserName . Как я могу это сделать?

Я добавил AuthorName , которое возвращало бы только те данные, из которых я хочу получить ApplicationUser . К сожалению, мне все еще нужно .Include(x=>x.Author) заставить это свойство работать. Могу ли я как-то исключить включение Author в процессе сериализации данных (чтобы скрыть это при отправке данных пользователю)?

Я знаю, что могу использовать .Select() метод, но это требует от меня ввода всех свойств, которые мне понадобятся. Если я изменю свою модель в будущем, мне нужно будет обновить все те, .Select() которые будут неудобными и пустой тратой времени.

Как бы вы это решили?

Ответ №1:

Вам нужно создать объект Dto и присвоить ему значения, а вместо этого вернуть Dto.

Dto

 public class WordDto 
{
    public string Title { get; set; }
    public DateTime? WhenCreated { get; set; }
    public string AuthorName { get; set; }
}
  

Затем в вашем действии

 [HttpGet]
public async Task<IEnumerable<WordDto>> Get()
{
    return _db.Words
              .Include(x=>x.Author)
              .Select(x =>
                  new WordDto 
                  {
                      Title = x.Title,
                      DateTime = x.WhenCreated,
                      AuthorName = x.Author?.UserName ?? string.Empty
                  }
              )
              .ToListAsync();
}
  

Комментарии:

1. Я бы удалил .ToList() , это кажется более правильным, пусть контроллер материализует запрос, когда это требуется

2. Извините, но я не до конца понимаю это. Использование Select таким образом вернет только некоторую UserName переменную (я не уверен, что это такое, ее нет в Word классе). Я хочу вернуть все свойства Word класса и одно из свойств Author

3. Затем вам нужно создать объект Dto и выполнить присвоение / сглаживание для него

4. Это то, чего я не хотел делать, потому что каждое небольшое изменение в основной модели потребует изменений во всех местах, с которыми я ее использую .Select() . Это, конечно, решает эту проблему, но я рассматриваю это как временное решение.

5. Вы никогда не должны возвращать модели сохраняемости из служб REST, последствия изменения базы данных намного хуже, чем необходимость обновлять модели: все приложения, которые используют этот RestAPI, должны будут обновляться при каждом изменении сохраняемости. Это намного хуже. Вот почему вы абстрагируете уровень доступа к данным в репозитории или обработчики запросов

Ответ №2:

Вы можете попробовать это, как показано ниже.

Примечание: Вам не нужно использовать Include здесь.

     [HttpGet]
    public async Task<IEnumerable<Word>> Get()
    {
        return _db.Words.Select(x => new  
                      {
                          Word = x,
                          AuthorName = x.Author.UserName
                      }
                  ).ToList();
    }
  

Комментарии:

1. К сожалению, отображается ошибка: «Недопустимый аноним-член типа declarator. Члены анонимного типа должны быть объявлены с назначением члена, простым именем или доступом к члену»

2. Как вы можете это сделать, x.Author.Select(y => new { y.UserName }) когда Author свойство не является IEnumerable ?

3. О .. Исправлено. Спасибо @Brad

4. В EF Core вы обнаружите, что вам действительно нужно выполнить, Include(x => x.Author) иначе Author свойство будет равно null.

5. Это никак не компилируется, вы не можете неявно преобразовать анонимный тип в класс

Ответ №3:

Создайте модель представления и используйте AutoMapper для заполнения. Посмотрите на использование AutoMapper и расширения ProjectTo https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions

Таким образом, если вы добавите свойства в View model, они будут автоматически сопоставлены, если они существуют в вашей модели EF

Итак, создайте виртуальную машину с требуемыми свойствами, названными соответствующим образом (см. Документы AutoMapper по соглашениям об именовании):

 public class WordVM 
{
    public string Title { get; set; }
    public DateTime? WhenCreated { get; set; }
    public string AuthorUserName { get; set; }
}
  

Затем используйте AutoMapper для проекта (он выполнит любые требуемые включения, поэтому, если вы измените виртуальную машину позже, он справится с этим)

   _db.Words.ProjectTo<WordVM>().ToList();
  

Вам не нужно, чтобы NotMapped автоматическое отображение свойств отображало свойство навигации Author и свойство автора UserName на AuthorUserName

Ответ №4:

Мой обходной путь состоял в том, чтобы получить все связанные объекты с помощью .include(), затем перебрать их и опустить значения свойств, которые я не хотел возвращать. Это потребовало бы некоторого обслуживания в случае изменения вашей модели, но, что удивительно, это не сильно повлияло на время отклика.