Создать один ленивый и один нетерпеливый DbContext через наследование?

Я использую EFCore 3.1.5, и у меня есть DbContext, который я хочу использовать в том же контроллере или службе, как ленивый, так и нетерпеливый. Однако, похоже, я не могу заставить его правильно загружать ленивый. Похоже, нетерпеливый работает нормально.

Всякий раз, когда я делаю что-то простое, как:

var users = await _lazyDbContext
    .Users
    .Take(10)
    .ToListAsync();

каждое свойство навигации на каждом User имеет значение NULL. Однако при активной загрузке все работает нормально:

var users = await _dbContext
    .Users
    .Include(x => x.Contact)
    .Take(10)
    .ToListAsync();

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<LazyUserContext>((sp, opt) =>
        {
            var connectionString = "very secret";
            opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
            opt.UseLazyLoadingProxies();
        });

    services.AddDbContext<UserContext>((sp, opt) =>
        {
            var connectionString = "very secret";
            opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
        });

    services.AddScoped<IUserContext, UserContext>();
    services.AddScoped<ILazyUserContext, LazyUserContext>();
}

UserContext.cs

public interface IUserContext
{
    DbSet<User> Users { get; set; }

    DbSet<Contact> Contacts { get; set; }
}

public class UserContext : DbContext, IUserContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options) {}

    public DbSet<User> Users { get; set; }

    public DbSet<Contact> Contacts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Users>(e => 
        {
            e.HasOne(x => x.Contact).WithOne(x => x.User).HasForeignKey(x => x.ContactId);
        }
    }
}

LazyUserContext.cs

public interface ILazyUserContext : IContext {}

public class LazyUserContext : UserContext, ILazyUserContext
{
    public LazyUserContext(DbContextOptions<UserContext> options) : base(options) {}
}

В чем может быть проблема? Я попытался использовать IoC как для интерфейса, так и для класса в моем контроллере / службе. Я пробовал с services.AddScoped<>() и без него.

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


person MortenMoulder    schedule 16.06.2020    source источник
comment
это может показаться глупым, но попробуйте поменять их opt.UseSqlServer(connectionString, x => x.CommandTimeout(300)); opt.UseLazyLoadingProxies(); так opt.UseLazyLoadingProxies(); opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));   -  person Seabizkit    schedule 16.06.2020
comment
@Seabizkit К сожалению, это не имеет значения. Я знаю, что ленивая загрузка работает, потому что, если я просто удалю весь LazyContext материал, чтобы остался только 1 DbContext, ленивая загрузка сработает отлично.   -  person MortenMoulder    schedule 16.06.2020
comment
хорошо, ммм, я думаю, как вы его зарегистрируете, иначе ... вам нужно повторно использовать определенный способ, получив его экземпляр services.AddScoped<DbContext>(provider => provider.GetService<ParadoxCoreContext>()); это из моего собственного проекта, но подправьте его в соответствии с потребностями ... попробуйте это   -  person Seabizkit    schedule 16.06.2020
comment
На этой странице документы. microsoft.com/en-us/dotnet/api/ упоминает о возможном звонке services.AddEntityFrameworkProxies()   -  person Neil    schedule 16.06.2020
comment
@Seabizkit Ваш DbContext - это настоящий DbContext класс Entity Framework или мой LazyUserContext? Потому что нет, это не сработает.   -  person MortenMoulder    schedule 16.06.2020
comment
@Neil Нет, свойства навигации по-прежнему равны нулю. Я добавил services.AddEntityFrameworkProxies() перед обоими своими services.AddDbContext() и пробовал добавить его после.   -  person MortenMoulder    schedule 16.06.2020
comment
@MortenMoulder, это то, что я имел в виду services.AddScoped<ILazyUserContext, LazyUserContext>((provider => provider.GetService<LazyUserContext>() ); вам нужно указать регистру в версии с ограниченной областью действия, чтобы использовать уже определенную конструкцию, где вы указали UseLazyLoadingProxies, ну, это мое понимание, иначе как services.AddScoped<ILazyUserContext, LazyUserContext>(); узнает, как построить LazyUserContext ...   -  person Seabizkit    schedule 16.06.2020
comment
@Seabizkit Ах да, в этом есть смысл   -  person MortenMoulder    schedule 16.06.2020
comment
@MortenMoulder с удовольствием, так что в первый раз я не стал более ясным ... ты только что выглядел так ловко, я подумал, ты поймешь, на что я намекал.   -  person Seabizkit    schedule 16.06.2020


Ответы (2)


Решение

Все, что я хочу, - это иметь возможность использовать ленивый dbContext или нетерпеливый dbContext

У вас должна быть возможность просто установить конфигурацию в подклассах:

public class LazyContext : MyContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseLazyLoadingProxies();
    }
}

public class EagerContext : MyContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {

    }
}

Есть два варианта настройки конфигурации параметра db: через конструктор (и, следовательно, маршрут DI) или через сам класс. Поскольку этот конкретный параметр зависит от класса, и вы не хотите манипулировать им при регистрации DI, имеет смысл полагаться на метод конфигурации, зависящий от класса.


Почему ваше решение не сработало

Причина, по которой ваш первоначальный подход не сработал, заключается в том, что AddDbContext<T> регистрирует этот конкретный T как ваш тип зависимости. Из источника:

Метод расширения AddDbContext по умолчанию регистрирует типы DbContext с ограниченным временем существования.

Обратите внимание, что он регистрирует тип контекста, а не какие-либо интерфейсы / предки этого типа контекста.

Итак, когда вы это сделаете:

services.AddDbContext<LazyUserContext>((sp, opt) =>
{
    var connectionString = "very secret";
    opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
    opt.UseLazyLoadingProxies();
});

services.AddScoped<ILazyUserContext, LazyUserContext>();

Если ваш класс имеет зависимость типа ILazyUserContext, он слушает только вторую регистрацию, а Flatout игнорирует первую.

Только когда ваш класс имеет зависимость LazyUserContext, вы действительно получите параметры контекста db, которые вы указали при первой регистрации.


Обратите внимание, что вы можете использовать это поведение регистрации для конкретного типа в своих интересах, если хотите зарегистрировать реализацию по умолчанию:

// Specific interface => specific type
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<ILazyUserContext, LazyUserContext>();

// General interface => explicitly chosen default type
services.AddScoped<IContext, UserContext>();

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

person Flater    schedule 16.06.2020
comment
this.Configuration не принадлежит моему классу. Вы уверены, что он существует в EFCore 3.1.5? Кроме того, похоже, что у вас есть ошибка копирования / вставки в вашем первом разделе. 2x LazyContext и 2x Enabled = true - person MortenMoulder; 16.06.2020
comment
это в основном подкрепляет мой комментарий по заданному мною вопросу и должен быть способом заставить его работать, не делая этого ... хотя в этом нет ничего плохого. - person Seabizkit; 16.06.2020
comment
@MortenMoulder: Вы правы, я скопировал не тот фрагмент. Я обновил фрагмент до EF Core, хотя суть ответа практически не изменилась. - person Flater; 16.06.2020
comment
@Flater Вместо EagerContext, нельзя ли просто использовать MyContext из вашего примера? - person MortenMoulder; 16.06.2020
comment
@MortenMoulder: Ага, если предположить, что это единственное различие между ними. Хотя для ясности я обычно советую иметь два явно описательных имени класса вместо того, чтобы только одно было помечено как ленивый, а другое не помечено как нетерпеливое. - person Flater; 16.06.2020
comment
@MortenMoulder: обратите внимание, что если вы сохраните и EagerContext, и MyContext, вы можете применить то, что я обсуждал в третьем разделе ответа, где MyContext может возвращать любой контекст (как свободно выбираемый вариант по умолчанию), тогда как EagerContext всегда возвращает нетерпеливый. В этом примере я использовал интерфейсы, но он работает одинаково для базовых типов. - person Flater; 16.06.2020
comment
Спасибо за помощь, @Flater. Я уже слишком много часов пытаюсь заставить это работать, и я, моя команда и кто-то из SO считают, что это ошибка: stackoverflow.com/questions/60453551/ Я в основном сталкиваюсь с проблемой, когда свойство имеет значение null. Я опубликую отчет об ошибке с командой EFCore на GitHub. Может они знают, что случилось - person MortenMoulder; 16.06.2020

Проблема вызвана тем, что оба конструктора контекста используют (зависят от) один и тот же тип опций - DbContextOptions<UserContext>.

AddDbContext<TContext> фактически регистрирует два типа - сам контекст TContext, а также фабрику для зависимости опций контекста DbContextOptions<TContext>.

Итак, вы регистрируете две фабрики опций (вместе с действием конфигурации) - DbContextOptions<UserContext> и DbContextOptions<LazyUserContext>. Однако, как упоминалось в начале, LazyUserContext зависит от DbContextOptions<UserContext>, поэтому он просто создается с первой настройкой параметров, то есть точно так же, как и другие.

Это не относится к отложенной загрузке, но для любого сценария, который требует различных параметров (другой тип базы данных / строка подключения и т. Д.) И является причиной существования универсального класса DbContextOptions<TContext>.

Решение - изменить LazyUserContext зависимость

public LazyUserContext(DbContextOptions<LazyUserContext> options) : base(options) { }

и поскольку это не будет компилироваться, потому что база ожидает DbContextOptions<UserContext>, добавьте второй защищенный конструктор к базовому классу, принимающему только DbContextOptions

protected UserContext(DbContextOptions options) : base(options) { }
person Ivan Stoev    schedule 16.06.2020