The method implemented EFCore horizontal component database table

The level of sub-table

When a large amount of data in our database table enough to affect performance, the general can use two solutions.
1, add an index.
2, the use of sub-table, and warehouses strategies.
There are two sub-table strategy, horizontal and vertical sub-table sub-table.
Vertical table idea is to separate frequently used in the tables to another table storage field, improving search efficiency.
The idea is to level the sub-table data table according to certain rules, assigned to the same table structure other data table, to reduce the load of a single table.
In the development of ASP.net Core, for the operation of the database almost inseparable EFCore, then the points table if the implementation level under the circumstances of use EFCore how to achieve it? Known, EFCore a map on a data table class, a class map now wants more data tables, from implementation point of view there are two schemes.
1, implemented in a database stored by the operation procedure and the like.
2, the dynamically mapped to different code data table according to the rules.
Since I am not a professional database developers, so here I realized in the second scenario.

Code Operating Environment

EFCore Version: 2.2.6
Test Console procedures: .net core 2.2
database providers: MySql.Data.EntityFrameworkCore 8.0.17

Upon examination, EFCore 3.0 or more can not use this method of, EFCore of 3.0 or more venue implementation code directly to hereinafter EFCore 3.x section.

Implementation

Here Post class corresponds to two data tables: post_odd and post_even

Code

Program.cs

using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;

namespace TableMappingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string table1 = "post_odd";
            string table2 = "post_even";
            BloggingContext context = new BloggingContext();
            
            // step1:改变实体模型缓存工厂的返回值,使EFCore认为Model已经发生改变,下次使用实体前将更新模型映射
            DynamicModelCacheKeyFactory.ChangeTableMapping();

            // step2:获取实体模型Post的映射(这里使用了实体模型,所以会更新模型映射)
            if (context.Model.FindEntityType(typeof(Post))?.Relational() is RelationalEntityTypeAnnotations relational)
            {
                // step3:修改Post实体映射的数据表
                relational.TableName = table1;
            }

            // 此时该context内Post实体的映射表已经是 post_odd, 就算重复以上3步也不会改变,除非重新new一个
            List<Post> list1 = context.Set<Post>().Where(s => true).ToList();
            Console.WriteLine(table1);
            PrintList(list1);

            // 改另一个表测试
            BloggingContext context_1 = new BloggingContext();
            DynamicModelCacheKeyFactory.ChangeTableMapping();

            if (context_1.Model.FindEntityType(typeof(Post))?.Relational() is RelationalEntityTypeAnnotations r)
            {
                r.TableName = table2;
            }
            List<Post> list2 = context_1.Set<Post>().Where(s => true).ToList();
            Console.WriteLine(table2);
            PrintList(list2);

            Console.ReadKey();
        }

        static void PrintList(List<Post> list)
        {
            foreach(Post item in list)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine();
        }
    }
}

Models.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace TableMappingTest
{
    /// <summary>
    /// 用于替换的模型缓存工厂
    /// </summary>
    public class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
    {
        private static int m_Marker = 0;
        
        /// <summary>
        /// 改变模型映射,只要Create返回的值跟上次缓存的值不一样,EFCore就认为模型已经更新,需要重新加载
        /// </summary>
        public static void ChangeTableMapping()
        {
            Interlocked.Increment(ref m_Marker);
        }
        
        /// <summary>
        /// 重写方法
        /// </summary>
        /// <param name="context">context模型</param>
        /// <returns></returns>
        public object Create(DbContext context)
        {
            return (context.GetType(), m_Marker);
        }
    }
    
    // Context模型
    public class BloggingContext : DbContext
    {
        public virtual DbSet<Blog> Blogs { get; set; }
        public virtual DbSet<Post> Posts { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // step0: 调用ReplaceService替换掉默认的模型缓存工厂
            optionsBuilder.UseMySQL("连接字符串")
                            .ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>()
                            .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(entity =>
            {
                entity.HasKey(e => e.BlogId);
                entity.ToTable("blog");
                entity.HasIndex(s => s.UserId)
                    .HasName("blog_user_FK_index");
                entity.Property(e => e.BlogId)
                    .HasColumnName("blogid")
                    .HasColumnType("int(11)")
                    .ValueGeneratedOnAdd();
                entity.Property(e => e.Rating)
                    .HasColumnName("rating")
                    .HasColumnType("int(11)");
                entity.Property(e => e.UserId)
                    .HasColumnType("int(11)")
                    .HasColumnName("userId");
            });
            modelBuilder.Entity<Post>(entity =>
            {
                entity.HasKey(e => e.PostId);
                entity.ToTable("post");
                entity.HasIndex(e => e.BlogId)
                    .HasName("post_blog_FK_idx");
                entity.Property(e => e.PostId)
                    .HasColumnName("postid")
                    .HasColumnType("int(11)")
                    .ValueGeneratedOnAdd();
                entity.Property(e => e.Title)
                    .HasColumnName("title")
                    .HasMaxLength(64);
                entity.Property(e => e.Content)
                    .HasColumnName("content")
                    .HasMaxLength(1024);
                entity.Property(e => e.BlogId)
                    .HasColumnName("blogId")
                    .HasColumnType("int(11)");
                entity.HasOne(e => e.Blog)
                    .WithMany(s => s.Posts);
            });
        }
    }
    
    public class Blog
    {
        public Blog()
        {
            Posts = new HashSet<Post>();
        }
        public int BlogId { get; set; }
        public string Url { get; set; }
        public int Rating { get; set; }
        public int UserId { get; set; }
        
        [DefaultValue(null)]
        public ICollection<Post> Posts { get; set; }

        // 为了方便测试就重写了ToString方法
        public override string ToString()
        {
            return $"Id: {BlogId}   Url: {Url}";
        }
    }
    
    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public int BlogId { get; set; }
        
        [DefaultValue(null)]
        public Blog Blog { get; set; }
        
        // 为了方便测试就重写了ToString方法
        public override string ToString()
        {
            return $"Id: {PostId}   Title: {Title}";
        }
    }
}

Operating results
Here Insert Picture Description
can be seen, with a query expression, reading different data tables.

Implementation steps

As the same code, the following steps:
1. We define a class ModelCacheKeyFactory themselves, IModelCacheKeyFactory implement an interface to a Create method returns an object
action: EFCore will use this object, call the object's methods Equals judgment map model has changed, if changed, will not use the old cache recall OnModelCreating load a new mapping model.
2. ModelCacheKeyFactory class we define EFCore replace the default factory configuration of the form class. Call ReplaceService in OnConfiguring method of Context.
3. When we need to replace the mapping table, find ways to make the Create method ModelCacheKeyFactory class of our own definition of return different values. Here I used a static variable way from growing.
4. context.Model.FindEntityType ({Type}). Relational () method of obtaining a solid model mapping.
The setting data of the mapping table.
Note that because dynamically modify the mapping between DbContext here will affect all threads that DbContext, so it is not thread safe. Virtually all dynamically modified based on EFCore table mappings programs are almost impossible thread-safe, so if you want to modify the table in EFCore dynamic mapping relationship had to be taken to avoid multiple threads sharing DbContext.

EFCore 3.x

Upon examination, after EFCore 3.0, has been unable to use the Relational IEntityType () method to get RelationalEntityTypeAnnotations, speculation may be because it is not safe, then dynamically modify the mapping table after DbContext created out may affect other threads, for example it is possible to thread a and B DbContext threads simultaneously access a database, but a thread halfway to modify the mapping table, B thread just need to undo some operations because it threads a mapping relationship changed, so the operation thread B affected to.
So if you want to modify the dynamic mapping table can dynamically specify when DbContext object has been created and to ensure DbContext mapping relationships within the life cycle of the table can not be changed, if you want to change only to re-create a DbContext object. This requires () method in doing the article in OnModelCreating DbContext, we need to clearly specify when calling this method which table the type mapping, which means that, if we are to achieve dynamic switching map, it must be added a layer of packaging, when switching to recreate DbContext according to the new mappings.
Please refer to the specific implementation
https://github.com/YinRunhao/DataAccessHelper/tree/EFCore31

Further package

Although the above example is very simple, but the functionality is achieved. If the need to apply to the project, such a degree package is not enough, you also need the code further encapsulation. Here is what I own more than a simple package code, hoping to help needy students.

Only suitable for version 3.0 EFCore
https://github.com/YinRunhao/DataAccessHelper/tree/master
apply to EFCore 2.x and 3.x
https://github.com/YinRunhao/DataAccessHelper/tree/EFCore31

Knowledge summary

1.EFCore a default class map is a data table, and implementation class by calling OnModelCreating methods (or other methods), and a data attribute mapping tables, fields and the like.
2. You can inject your own implementation IModelCacheKeyFactory factory class by calling ReplaceService method OnConfiguring method.
There mapping relationship 3.EFCore caching mechanism, under normal circumstances will only be called once OnModelCreating mapping relationship in the context entity is used for the first time, and then cached mapping relationship.
4. by way of self-realization IModelCacheKeyFactory change the caching behavior EFCore (cache can never be changed or have changed after the cache, etc.).
5.EFCore mapping relationship caching behavior is determined by the Create method IModelCacheKeyFactory derived class, the same value if the Create method returns the last value cache and will not call OnModelCreating method to update mappings.
6. Create a value derived class to make IModelCacheKeyFactory returned last time are not the same, and the method is not necessarily Equals To override the GetHashCode; by returning a tuple and a tuple of type values are not the same i.e. can (Microsoft documentation of Sao operation).
If this article had the opportunity to help to you, please do not mean your praise.

Reference article

EntityFrameworkCore implemented using Repository, UnitOfWork, MySQL support sub-library sub-table
using EntityFrameworkCore implemented Repository, UnitOfWork, MySQL support sub-library sub-table
EFCore document: switching between a plurality of models having the same type of DbContext

Published 15 original articles · won praise 2 · Views 4856

Guess you like

Origin blog.csdn.net/weixin_38138153/article/details/101997138