EntityFrameworkコアの動的ローディングモデルきっぱりと、我々はそれについて知りたいですか?

序文

この記事では、子供用の靴から来て私に尋ねた:どのように動的にEntityFrameworkコアでモデルをロードするために?この問題にEntityFramworkの学習に対応答えの公園友達となっている問題の最終的解決は、靴が提起したが、ときに私の再深度調査し、問題がはるかに単純なものからであることがわかった、けれども、それは、あまりにも多くの研究ではありません問題は、それは私に、これは貧しい子供用の靴を助けるために、次のレコードを行うことができるかもしれませんので、個人的には、貴重な、必要な感じだと思うには少し時間がかかりました。この価値から生じる、研究コースEntityFrameworkコアの動的ローディングモデルは、このように始まります、その後、私の足跡をたどるが見に行ってきました。

EntityFrameworkコアダイナミックローディングモデル

我々はまだスクラッチ、EFコア2.xのコンソールアプリケーションを作成し、我々はモデルを使用する必要があり、このセクションを与える、いつものように私たちはブログとポストを使用している、次のように:

パブリック クラスブログ
{ 
    公共 のint Idを{ 取得 ; セット; } 

    パブリック 文字列名前{ 得ますセット; } 

    公共の一覧<お知らせ>記事{ 取得しますセット; } 
}
    ///  <要約> 
    /// 博客文章
     ///  </要約> 
パブリック クラスのポスト
 { 
     公共 のint Idを{ 取得 ; セット; }
      公共 のintのblogid { 得ますセット; }
      パブリック 文字列のタイトル{ GETセット; }
      パブリック 文字列の内容{ 取得しますセット; }
      公共ブログブログ{ GETセット; } 
 }

次のように次は、私たちが使用する必要がある状況です。

パブリック クラスEFCoreDbContext:DbContext 
{ 
        保護された オーバーライド 無効OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         => optionsBuilder.UseSqlServer(@ " サーバー=;データベース= EFTest; Trusted_Connection =真; " ); 

        公共 DbSet <ブログ>ブログ{ 取得しますセット; }
         公共 DbSet <ポスト>記事{ 取得しますセット; } 

}

次のように作成され、最終的なデータベーステーブル:

私たちは、次のように私たちは、リストをブログ問い合わせる、上記の表名は、モデルの複数形である参照してください。

VaRの状況は= 新しいEFCoreDbContext();
VARのブログ= context.Blogs.Include(D => d.Posts).ToList();

上記プレゼンテーションは、ビジネスの変化、我々は複数のモデルのコンテキストでDbSetプロパティを追加する必要があり、この操作を繰り返さないようにする能力、当社のその後の添加と一緒にいつもの練習、それは頼まれたこの時点であり、我々はそれに焦点を当て、ビジネスの効率を向上させるために、モデルは、動的コンテキストにロードされましたか?コースは広いで、ここでは、実際のシナリオでは、我々は通常、上記のブログやポストモデルクラスライブラリの下に表示されるように、モデルライブラリに置かれ、説明を使用します。

セットモデル取得プログラムが配置され、以下のように、私たちが行う必要がある次は、モデルの初期化で、その後のModelBuilderによって生成されたモデルの組み立ては、通常の状況下では、我々は、以下の方法エンティティ配分モデルを呼び出しているされています。

modelBuilder.Entity <ブログ>(typebuilder =>  
{ 
       ... 
})。

上記の分析では、我々は次のように得られた呼び出しModelBuilderので反射されたメソッドを呼び出し反射エンティティとエンティティを介して、上述した方法を得ます。

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            var assembly = Assembly.Load("Model");

            var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[] { });

            var entityTypes = assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && !t.IsNested);

            foreach (var type in entityTypes)
            {
                entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
            }

            base.OnModelCreating(modelBuilder);
        }

当然上述加载模型程序集的方式根据我们实际项目情况而定,同时在我们过滤程序集中类型时也同样如此,比如若是DDD架构,对于仓储都会封装一层进行基本操作的仓储,此时其他模型仓储必派生于基仓储,通过基本仓储模型进行过滤等等。接下来我们将上下文中添加的DbSet<Blog>和DbSet<Post>给去掉,如下:

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

然后我们直接通过上下文中的Set方法来查询数据,如下:

            var context = new EFCoreDbContext();

            context.Database.EnsureCreated();

            var blogs = context.Set<Blog>().Include(d => d.Posts).ToList();

上述我们多添加了一行确保数据库模型已提前被创建,这是必要的,其背后本质就是通过命令进行迁移,要不然在加载模型时应该会报错,当然若在Web应用程序中,我们在Configure方法中也同样添加如下一行:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, EFCoreDbContext context)
    {
        context.Database.EnsureCreated();

         ......
    }

此时将会抛出上述异常,这是为何呢?这是因为数据库表名是和如上上下文中我们已经注释掉的DbSet包含的模型属性名称一致,若我们将上述DbSet包含的模型属性的注释给去掉,当加载DbSet属性时将获取该属性名称和我们配置的Schema作为架构名称(不配置,默认为空),我们通过如下源码可得知(当然我们通过SQL Server Profiler生成的SQL语句也可得知)

上述我们只是得到最终表的架构和名称而已,那么默认表名称是怎样的呢?当我们查询时,会从上述DatasetTable类中去获取表名,如下:

    public class DatabaseTable : Annotatable
    {
        /// <summary>
        ///     The database that contains the table.
        /// </summary>
        public virtual DatabaseModel Database { get; [param: NotNull] set; }

        /// <summary>
        ///     The table name.
        /// </summary>
        public virtual string Name { get; [param: NotNull] set; }
        ......
    }

它具体是什么时候调用的呢,我们看如下代码:

        protected virtual EntityTypeBuilder VisitTable([NotNull] ModelBuilder modelBuilder, [NotNull] DatabaseTable table)
        {
         
            var entityTypeName = GetEntityTypeName(table);

            var builder = modelBuilder.Entity(entityTypeName);

            var dbSetName = GetDbSetName(table);
            builder.Metadata.SetDbSetName(dbSetName);

            if (table is DatabaseView)
            {
                builder.ToView(table.Name, table.Schema);
            }
            else
            {
                builder.ToTable(table.Name, table.Schema);
            }

            if (table.Comment != null)
            {
                builder.HasComment(table.Comment);
            }

            ......
            return builder;
        }

到了这里我们并未看到任何有效的信息,只是将该类中得到的表名和架构设置到ToTable方法中,让我们从头开始梳理思路,因为从一开始我们并未通过注解或者Fluent APi去显式配置表名,所以此时必将走EntityFramework Core的默认约定,思路已经很清晰,最终我们找到获取表名的方法,如下:

        private static void TryUniquifyTableNames(
            IConventionModel model, Dictionary<(string, string), List<IConventionEntityType>> tables, int maxLength)
        {
            foreach (var entityType in model.GetEntityTypes())
            {
                var tableName = (Schema: entityType.GetSchema(), TableName: entityType.GetTableName());
                if (!tables.TryGetValue(tableName, out var entityTypes))
                {
                    entityTypes = new List<IConventionEntityType>();
                    tables[tableName] = entityTypes;
                }
                ......
            }
        }    

到这里我们看到了获取表名的方法,我们继续往下走,看看具体是如何获取表名的呢?

         public static string GetTableName([NotNull] this IEntityType entityType) =>
            entityType.BaseType != null
                ? entityType.GetRootType().GetTableName()
                : (string)entityType[RelationalAnnotationNames.TableName] ?? GetDefaultTableName(entityType);

因为对应类型并未有其基类,接下来去获取注解的表名,此时我们也并未通过注解设置表名,到这里我们也能明白若是我们通过注解在对应模型上添加与数据库表名一致的复数即可解决问题。我们继续往下走,最后调用获取默认表名的方法:

        public static string GetDefaultTableName([NotNull] this IEntityType entityType)
        {
            var ownership = entityType.FindOwnership();
            if (ownership != null
                && ownership.IsUnique)
            {
                return ownership.PrincipalEntityType.GetTableName();
            }

            return Uniquifier.Truncate(
                entityType.HasDefiningNavigation()
                    ? $"{entityType.DefiningEntityType.GetTableName()}_{entityType.DefiningNavigationName}"
                    : entityType.ShortName(),
                entityType.Model.GetMaxIdentifierLength());
        }

首先我们并未设置模型的OwnType,接下来调用方法根据注释意为:获取模型是否有定义的导航类型,看到这里时,我认为Post不就是Blog的导航吗,此方法被暴露出来可供我们调用,当我去验证时发现结果却返回false,不禁让我心生疑窦

        /// <summary>
        ///     Gets a value indicating whether this entity type has a defining navigation.
        /// </summary>
        /// <returns> True if this entity type has a defining navigation. </returns>
        [DebuggerStepThrough]
        public static bool HasDefiningNavigation([NotNull] this IEntityType entityType)
            => entityType.DefiningEntityType != null;

通过其方法解释实在不解导航具体指的啥玩意,于是乎我在github上提了对该方法的疑惑《https://github.com/dotnet/efcore/issues/19559》,根据解答,即使配置了owned Type依然返回false(这个问题后续再详细分析下源码),接下来继续往下看ShortName方法,如下:

        /// <summary>
        ///     Gets a short name for the given <see cref="ITypeBase" /> that can be used in other identifiers.
        /// </summary>
        /// <param name="type"> The entity type. </param>
        /// <returns> The short name. </returns>
        [DebuggerStepThrough]
        public static string ShortName([NotNull] this ITypeBase type)
        {
            if (type.ClrType != null)
            {
                return type.ClrType.ShortDisplayName();
            }

            var plusIndex = type.Name.LastIndexOf("+", StringComparison.Ordinal);
            var dotIndex = type.Name.LastIndexOf(".", StringComparison.Ordinal);
            return plusIndex == -1
                ? dotIndex == -1
                    ? type.Name
                    : type.Name.Substring(dotIndex + 1, type.Name.Length - dotIndex - 1)
                : type.Name.Substring(plusIndex + 1, type.Name.Length - plusIndex - 1);
        }

到这里我们总算明白了,模型类型不为空获取模型的名称,经验证其ShortDisplayName方法返回值就是模型名称即Blog,所以才抛出最开始异常对象名无效,我们也可通过如下代码验证表名是不是Blog

            var mapping = context.Model.FindEntityType(typeof(Blog)).Relational();
            var schema = mapping.Schema;
            var tableName = mapping.TableName;

注意:若您是EntityFramework Core 3.x版本上述获取架构和表名等方式已经修改成直接针对模型的扩展方法。如下:

            var mapping = context.Model.FindEntityType(typeof(Blog));
            var schema = mapping.GetSchema();
            var tableName = mapping.GetTableName();

所以对于EF Core而言,默认的表名就是模型名称,若我们以DbSet属性暴露模型则以DbSet属性名称作为表名,同样我们也验证下,我们将最开始注释掉的DbSet<Blog> Blogs,修改成如下:

 public DbSet<Blog> BlogAlias { get; set; }

所以若采用动态加载模型,如果数据库表名就是模型名称,那么没毛病,否则我们应该根据项目约定而需要进行相应的修改才行,如最开始给出的数据库表名为复数为例,此时我们还需修改数据库表名的约定,在OnModelCreating方法添加如下代码:

            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                var tableName = entityType.Relational().TableName;
                modelBuilder.Entity(entityType.Name).ToTable($"{tableName}s");
            }

同理针对EntityFramework Core 3.x版本修改成如上注意说明,接下来我们再次注释掉上述验证时暴露出的DbSet,最后查询结果如下:

事情还未结束,配置动态加载模型后,由上只是证明关系映射等没问题,接下来我们如下配置owned Type,我们将看到会抛出异常,很显然,虽然我们只是加载了模型,但是对于映射关系通过约定可以得到,而owned Type必须显式配置,所以在遍历生成模型时,我们恐怕还需要额外处理owned Type,遗留的这个问题等待空闲时再弄下,暂时就到这里吧。

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Post> Posts { get; set; }
    public Tag Tag { get; set; }
}

public class Tag
{
    public string Name { get; set; }
    public Blog Blog { get; set; }
}

modelBuilder.Entity<Blog>().OwnsOne(t => t.Tag).WithOwner(b => b.Blog);

总结

本节我们详细讲解了在EntityFramework Core如何动态加载模型,同时针对动态加载模型所带来的问题也只是进行了一丢丢的论述,来,我们下一个结论:在EntityFramework Core中根据约定表名为DbSet属性名称,若在上下文中未暴露DbSet属性,则表名为模型名称,如果采用动态加载模型,那么表名必须与模型名称一致,否则将抛出异常,当然我们也可以根据实际项目约定更改表名。通过本节动态加载模型将引入下一节内容:EntityFramework Core表名原理解析,感谢您的阅读,下一节内容相信很快就会到来。

おすすめ

転載: www.cnblogs.com/CreateMyself/p/12175618.html