Проектирование класса с использованием IOptions , обрабатываемых EF?

#c# #entity-framework #dependency-injection #appsettings

Вопрос:

У меня есть класс, содержащий некоторую информацию, которую я храню в базе данных.

 class SomeThing
{
  public Guid Id { get; set; }
  public string Name { get; set; }
  public string Info => "extra info";
}
 

Естественно, последнее свойство опущено EF by entity.Ignore(a => a.Info); . Теперь я хотел бы, чтобы текстовая дополнительная информация была настроена из appsettings.json , поэтому я представил поставщика опций и ввел ее с помощью конструктора (после его регистрации Startup.cs ). DI требует, чтобы я использовал параметризованный конструктор, в то время как EF требует конструктора без параметров, поэтому мой класс становится немного забитым.

 class SomeThing
{
  private IOptions<Config> Config { get; }

  public SomeThing() { }
  
  public SomeThing(IOptions<Config> config)
  { 
    Config = config;
  }

  public Guid Id { get; set; }
  public string Name { get; set; }
  public string Info => Config.Value.Info;
}
 

Теперь неуклюжая сложность заставляет меня подозревать, что мой дизайн несовершенен. Кроме того, я не вижу, как EF должен вводить значение параметров, поскольку он полагается на конструктор без параметров. Я рассматривал возможность реализации пустого конструктора и каким-то образом извлекать службы, вручную извлекая значение, но, на мой взгляд, это огромный красный флаг.

Как я должен подойти к дизайну такого класса?

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

1. Как правило, вы не смешиваете проблемы сущностей с опциями или DI. Зачем вам нужна Info ваша сущность?

2. Значит, что-то используется и как сущность, и как что-то другое? EF не будет запускать ваш DI, и ваш DI не будет загружать свои свойства из базы данных, так почему же? Пожалуйста, объясните свою проблему более подробно, в том числе, когда и как вы используете этот класс.

3. @TheGeneral Мне нужна строка для настройки URL-адресов. Это часть возвращаемого URL-адреса, который хранится в моем потоке авторизации для внешнего IDP. В зависимости от того, в какой среде мы выполняем, мне нужно поместить разные части, чтобы создать фактический URL-адрес для перенаправления.

4. @CodeCaster Я использую сущность в качестве «держателя» для входящих подключений (которые затем я передаю внешнему IDP), и когда пользователь авторизуется, я получаю ответный вызов на свой сервер. Мне нужно где-то сохранить сконструированный URL-адрес возврата, так как это не тот же сеанс, который открывается при возврате запроса от внешнего IDP. И поскольку я не могу сохранить его как статическое поле в памяти, мне нужно его сохранить. Часть сохраненного объекта было бы неплохо настроить с помощью файла настроек.

5. Я понятия не имею, что вы под этим подразумеваете или зачем вам нужна сущность для хранения этого URL-адреса.

Ответ №1:

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

ThingRepo.GetSomeThing() извлекает сущность и Info при необходимости заполняет свойство. Затем он может внедриться IOptions<T> в конструктор.

 class ThingRepo
{
    private Config _config;
    private DbContext _db;

    public ThingRepo(IOptions<Config> config, DbContext db)
    {
        _db = db;
        _config = config.Value;
    }

    public async Task<Thing> GetSomeThing()
    {
        var thing = await _db.Set<Thing>().FirstAsync();
        thing.Info = _config.Info;
        return thing;
    }
}
 

Это позволяет обойти ограничение необходимости требовать IOptions<T> в конструкторе сущностей.

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

1. Я тоже склоняюсь к этому. Возможно, создание фабричного фасада, обертывающего контекст БД и обогащающего объект перед хранением. И я согласен с вами, что это создает плохую атмосферу плохого дизайна. Однако я не уверен, как подойти к этому иначе. Мне нужно, чтобы мои данные сохранялись после запроса на авторизацию и до того, как поступит ответ на аутентификацию от внешнего IDP. Эта часть работает отлично (в значительной степени благодаря вам). Однако теперь я хотел бы, чтобы части краткосрочного хранилища можно было настраивать. И это-ухаб на дороге…

2. Если у вас есть мудрое предложение, не стесняйтесь стрелять. В противном случае дайте мне знать, что вы не видите ничего очевидного на основе предоставленной скудной информации, и я приму один из ответов в качестве ответа.

3. IdentityServer сохраняет запросы на авторизацию в PersistedGrantDbContext и в других случаях , когда ему необходимо временно хранить данные. Вы можете расширить его и присвоить идентификаторам, а также использовать для хранения всего, что связано с аутентификацией, вместе. Ознакомьтесь с DeviceFlowStore примером: github.com/DuendeSoftware/IdentityServer/blob/…

4. Это на самом деле очень интересное предложение. Я даже могу использовать его в качестве распределенного держателя для информации, так как сейчас мы запускаем несколько модулей. Мило! Что касается техники, я исправляюсь. Что касается ваших правок на мой вопрос: бум ! Я ничего не мог поделать. Извините. 🙂

5. Ничто не помешает пользователю вставить указанный DbContext в другой класс, в другом месте, где требуется эта сущность, и у нее не будет этого хранилища, ни логики, и Info свойство сохранит свое значение по умолчанию, т. е. null . Кроме того, Entity Framework уже реализует класс репозитория и так далее.

Ответ №2:

Поскольку SomeThing это определение сущности для вашей базы данных, я бы не стал пытаться использовать DI для ввода параметров. Я все равно не думаю, что это поддерживаемый сценарий в EF Core.

Если вам действительно нужен доступ к Info объекту, вы можете сослаться на другую службу, совместимую с DI, но опять же, не было бы проще получить доступ к ней напрямую из кода Info , из которого вы когда-либо вызывали доступ SomeThing

Обычно ядро EF предназначено для хранения данных в базе данных.