在上下文外部构建模型时的.Net Core延迟加载问题

时间:2019-04-30 11:37:10

标签: c# entity-framework-core lazy-loading ef-core-2.2

尽管在使所有方法保持虚拟的情况下在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”方法在Context外部构建了数据实体,但是这种方式的惰性加载支持消失了,并且不为从中获取的实体创建代理对象数据库。

2 个答案:

答案 0 :(得分:2)

问题在于,延迟加载代理程序包使用约定,该约定在构建模型并对其进行修改后执行。尽管外部模型是在没有该约定的情况下构建的,所以根本无法激活该功能。

以下解决方法适用于撰写本文时最新的官方EF Core版本2.2.4。如果您升级到新的EF Core版本(3.0+),则很可能需要对其进行相应的更新;如果他们对其进行了修复,则需要将其删除。

您使用的SqlServerConventionSetBuilder.Build()方法中的EF Core 2.2.4 code看起来像这样:

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时自动执行”

答案 1 :(得分:0)

在这种情况下,使用EFCore 2.2.4进行延迟加载的一种解决方法是将ILazyLoader服务注入到实体中。此方法不需要继承实体类型,也不要求导航属性是虚拟的,并且允许使用new创建的实体实例附加到上下文后就可以延迟加载。但是,它需要引用ILazyLoader服务,该服务在Microsoft.EntityFrameworkCore.Abstractions包中定义。延迟加载具有多对多关系的数据模型的示例代码如下:

    `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;
            }
        }`