.Net Core Lazyloading проблема, когда модель строится вне контекста - PullRequest
1 голос
/ 30 апреля 2019

Ленивая загрузка связанных объектов сущности не работает, когда модель строится вне OnModelCreating и связанного объекта, несмотря на то, что все методы остаются виртуальными.

Например.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    { 
        optionsBuilder
            .UseLazyLoadingProxies()
            .UseModel(new ModelBuilderService().GetOrCreateCompiledModel())
            .UseSqlServer(@"connectionstring",
                sqlOption => sqlOption.UseNetTopologySuite());

    }
}

public class ModelBuilderService 
{
    private static IModel GetOrCreateCompiledModel(IEnumerable<string> modelSupplyingAssemblyPatterns)
    {
        var conventions = SqlServerConventionSetBuilder.Build();
        var modelBuilder = new ModelBuilder(conventions);

        var modelBuilderType = typeof(ModelBuilder);
        var entityMethod = modelBuilderType.GetMethod("Entity", modelBuilderType.GetGenericArguments());
        var pathToUse = AppDomain.CurrentDomain.BaseDirectory;

        if (!AppDomain.CurrentDomain.BaseDirectory.Contains("bin"))
        {
            pathToUse = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin");
        }

        var entitiesAdded = new HashSet<string>();

        if (entityMethod == null)
        {
            throw new NullReferenceException("Cannot find Entity method on DbModelBuilder");
        }

        foreach (var assemblyPattern in modelSupplyingAssemblyPatterns)
        {
            var dataProviderModels = Directory.EnumerateFiles(pathToUse, assemblyPattern, SearchOption.AllDirectories);

            foreach (var dll in dataProviderModels)
            {
                var assembly = Assembly.LoadFrom(dll);

                modelBuilder.ApplyConfigurationsFromAssembly(assembly);

                var typesToRegister = assembly.GetTypesInheritingFrom<BaseObject>();

                foreach (var entity in typesToRegister)
                { 
                    if (entitiesAdded.Add(entity.FullName))
                    {
                        entityMethod.MakeGenericMethod(entity)
                                    .Invoke(modelBuilder, new object[] { });
                    }
                }
            }
        } 
        return modelBuilder.Model;
    } 
}

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

Ответы [ 2 ]

2 голосов
/ 02 мая 2019

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

Следующий обходной путь предназначен для последней официальной версии EF Core 2.2.4 на момент написания.Скорее всего, его необходимо будет соответствующим образом обновить, если вы обновитесь до более новой версии EF Core (3.0+) или удалите, если они это исправят.

Код EF Core 2.2.4 из *Используемый вами метод 1007 * выглядит следующим образом:

public static ConventionSet Build()
{
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkSqlServer()
        .AddDbContext<DbContext>(o => o.UseSqlServer("Server=."))
        .BuildServiceProvider();

    using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        using (var context = serviceScope.ServiceProvider.GetService<DbContext>())
        {
            return ConventionSet.CreateConventionSet(context);
        }
    }
}

Как видите, он использует некоторые хитрости, наиболее важным из которых является собственный DbContextOptionsBuilder.Поэтому все, что нам нужно, это добавить вызов UseLazyLoadingProxies() к этому сборщику.

Для этого создайте приватный статический метод с измененным кодом:

static ConventionSet BuildSqlServerConventionSet()
{
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkSqlServer()
        .AddDbContext<DbContext>(o => o.UseSqlServer("Server=.").UseLazyLoadingProxies()) // <--
        .BuildServiceProvider();

    using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        using (var context = serviceScope.ServiceProvider.GetService<DbContext>())
        {
            return ConventionSet.CreateConventionSet(context);
        }
    }
}

и используйте его вместо вызова SqlServerConventionSetBuilder.Build(), например,

var conventions = BuildSqlServerConventionSet();
// ... the rest

Обновление: Также обратите внимание, что свойство ModelBuilder.Model возвращает отложенную изменяемую модель во время построения.Чтобы получить готовую модель «готовую к использованию во время выполнения» , замените

return modelBuilder.Model;

на

return modelBuilder.FinalizeModel();

Этот метод выполняется инфраструктурой EF Core "автоматически при использовании OnModelCreating" .

0 голосов
/ 03 мая 2019

Одним из обходных путей для отложенной загрузки с EFCore 2.2.4 в этом сценарии является внедрение службы ILazyLoader в объект. Этот метод не требует, чтобы типы сущностей были унаследованы или свойства навигации были виртуальными, и позволяет экземплярам сущностей, созданным с помощью new, выполнять ленивую загрузку после присоединения к контексту. Однако для этого требуется ссылка на службу ILazyLoader, которая определена в пакете Microsoft.EntityFrameworkCore.Abstractions. Пример кода для отложенной загрузки Datamodel с отношением многие-многие будет следующим:

    `public partial class PersonOrganisation
        {
            private Person person;
            private Organisation organisation;
            private ILazyLoader LazyLoader { get; set; }
            private PersonOrganisation(ILazyLoader lazyLoader)
            {
                LazyLoader = lazyLoader;
            }
            public PersonOrganisation()
            {
            }

            public Guid? PersonId { get; set; }
            public Guid? OrganisationId { get; set; }

            public virtual Organisation Organisation {
                get => LazyLoader.Load(this, ref organisation);
                set => organisation = value;
            }

            public virtual Person Person {
                get => LazyLoader.Load(this, ref person);
                set => person = value;
            }
        }`
...