Как использовать UserManager из проекта A в проекте B? - PullRequest
0 голосов
/ 06 мая 2020

И проект A, и проект B являются ASP NET приложениями Core 2.2.

Проект B использует Hangfire для фоновых заданий и почти ничего не делает, а тот факт, что он использует Hangfire, может даже не быть важно (подробнее об этом внизу). Проект A ставит задания в очередь на Hangfire B.

Теперь предположим, что у меня есть класс, представляющий задачу, которая называется Job. Это содержится в проекте C, простой старой библиотеке классов, на которую ссылается проект B, и которая, в свою очередь, ссылается на другие проекты, содержащие объекты, с которыми он работает.

Зависимости должны быть введены в этот класс через конструктор :

public class Job
{
    public Job(UserManager<ApplicationUser> userManager,
               IThisRepository thisRepository,
               IThatRepository thatRepository)
    {
    }

    public void Execute(string userId)
    {
        // this is where the work gets done
    }
}

и по большей части они делают вводятся: IThisRepository и IThatRepository вводятся, и они работают ... в основном.

В Проект B Startup.cs, тот, который должен запускать это задание, я вручную и успешно зарегистрировал эти интерфейсы вместе с DbContext, для которых им требуются некоторые другие вещи.

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

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

public class Category
{
    [Key]
    public int Id { get; set; }

    // several other properties of primitive types

    public ApplicationUser User { get; set; }

    [Required]
    public string UserId { get; set; }
}

public class Dish
{
    [Key]
    public int Id { get; set; }

    // several other properties of primitive types

    public ApplicationUser User { get; set; }

    [Required]
    public string UserId { get; set; }        

    public Category Category { get; set; }

    [Required]
    public string CategoryId { get; set; }
}

теперь проблема в следующем: внутри Job я пытаюсь создать новый Di sh и связать его с обоими пользователь и категория. Поскольку у меня есть только идентификатор пользователя и нет доступа к UserManager, я пытаюсь сделать вот что:

// ...

var category = await categoryRepository.FindByUserAndCode(userId, "ABC");

// this is a category that is guaranteed to exist

var dish = new Dish();
dish.UserId = userId;
// notice there's no dish.User assignment, because I don't have an ApplicationUser object
dish.Category = category;

dishRepository.Upsert(dish); (which internally either creates a new entity or updates the existing one as appropriate)

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

Поскольку категория с кодом AB C для этого пользователя существует в базе данных, я подумал, что это странно.

Вот в чем дело: экземпляр Category, который возвращает репозиторий, действительно имеет заполненное свойство UserId, но свойство User равно null.

Я думаю, что это причина моей проблемы: EF, вероятно, видит, что свойство - null, и считает этот объект новым.

Я не знаю, почему он появляется null (и он даже для других сущностей, у всех которых есть свойство, ссылающееся на пользователя), но я попытался вернуться и, вместо того, чтобы использовать только идентификатор пользователя, я хотел попытаться заставить Hangfire создать экземпляр Job, вводя в него UserManager<ApplicationUser>, поэтому по крайней мере, я мог получить экземпляр моего пользователя по его идентификатору.

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

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

Я сказал, что факт, что я ' m с использованием Hangfire может не иметь значения, потому что предположение, при котором он работает: просто дайте мне имя вашего класса, я позабочусь о его создании , пока все зависимости были зарегистрированы.

Кто-нибудь делал это раньше и может помочь пролить свет?

1 Ответ

3 голосов
/ 06 мая 2020

Вы включили сюда огромное количество информации, которая полностью не имеет отношения к рассматриваемой проблеме. Ваша проблема сводится к тому, что вы получаете исключение при попытке добавить di sh: «категория с тем же идентификатором, который я пытаюсь вставить, уже присутствует, поэтому я пытаюсь дублировать первичный ключ. . "

Чаще всего это вызвано попыткой использовать отсоединенный объект в качестве отношения, например:

dish.Category = category;

Если category отсоединен от контекста, EF попытается создать его из-за этого назначения, а поскольку он уже существует, это создание не выполняется. Мы не можем видеть, что происходит в categoryRepository.FindByUserAndCode, но я предполагаю, что вы либо вызываете AsNoTracking с запросом, либо вручную создаете новый экземпляр Category. В любом случае этот экземпляр отделяется от контекста. Чтобы прикрепить его снова, вам просто нужно сделать:

context.Attach(category);

Однако у вас нет прямого доступа к вашему контексту здесь. Это еще одна причина, по которой вам никогда не следует использовать шаблон репозитория с EF . В течение года разработчики подвергались огромной боли и страданиям из-за плохих советов или из-за ошибочных попыток делать что-то, как они привыкли. о том, что это сам слой данных. DbContext - это единица работы, а каждый DbSet - это репозиторий ... уже. Шаблон репозитория предназначен для абстрагирования низкоуровневого доступа к базе данных (например, всего грубого построения SQL строк). EF уже является абстракцией высокого уровня, попытка втиснуть ее в другой слой шаблона репозитория только парализует ее и приводит к таким проблемам, как те, с которыми вы здесь столкнулись.

Короче говоря, проблема в том, что category отделен. Вам нужно либо убедиться, что он никогда не отключается (например, не использовать AsNoTracking), либо найти способ обеспечить его повторное подключение позже. Однако лучше всего здесь полностью выбросить весь этот мусор из репозитория и просто использовать контекст напрямую. Выбор использования ORM, такого как EF, означает просто использование стороннего DAL, а не написания собственного. Во всяком случае, писать свое собственное - неправильно. Вы используете встроенную структуру маршрутизации в ASP. NET Core. Вы используете встроенный шаблонизатор (например, Razor). Чувствуете ли вы необходимость добавить в них некоторой абстракции? Конечно, нет, так почему же DAL отличается? Если вы просто должны создать абстракцию, используйте осмысленную абстракцию, такую ​​как CQRS, уровень сервиса или шаблоны микросервисов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...