EntityFramework Core principles to resolve the table name, let me, you uncover the veil of mystery

Foreword

On one of our best start for exceptions thrown just kick the tires were resolved, the feeling is not a little something more to say, yes, I have this feeling, I believe you see here and I have some doubts, if we are going to , Fluent APi, DbSet respectively table following settings via annotations, whether it will throw an exception? If not, there is its priority, the priority in the end what is it? Built specifically how to achieve it? Let's start from scratch to uncover its mysterious veil.

EntityFramework Core principles to resolve the table name

In the end we will not know whether there is still its priority will throw an exception, then the next we are as follows (please refer to the model of a " https://www.cnblogs.com/CreateMyself/p/12175618.html ") principle analysis carried out:

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

[Table("Blog2")]
public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Post> Posts { get; set; }
}

modelBuilder.Entity<Blog>().ToTable("Blog3");

Before entering the principle has not been resolved, let's venture a guess as configured by the priority will be like? It is Fluent Api> Notes> DbSet> agreed to it? Assuming that this is the case, EntityFramework Core built-how to achieve it? Is the use of mechanisms covered by it? A bunch of questions emerge before our eyes, come, let us explore boring into the source of the world, for you one doubts. First, we need to be clear that before we instantiate the context of the operation, EntityFramework Core Specific done? The story must start with self DbContext context we derive, as follows:

    public class EFCoreDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(@"Server=.;Database=EFTest;Trusted_Connection=True;");

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

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(b =>
            {
                b.ToTable("Blog3");
            });

            base.OnModelCreating(modelBuilder);
        }
    }

Before EntityFramework Core, we use the context of the operation is in accordance with the above code from top to bottom on the overall preparatory work done the following three steps:

[1] When the instantiation context, to find DbSet attributes cached in memory, and

[2] In the context of the key as a cache, all of the model data in the cache memory in context, if not cached execution of steps [3].

[3] create all relevant data model context.

Find DbSet property and cache

Next, we analyze step by step, step by step approach to achieve the above three-step operation, when either active or add context to instantiate middleware on the Web, will have to go through all the interfaces we need to use dependency injection, of course, is EntityFramework Core the [  Microsoft.Extensions.DependencyInjection  ] library, as registered what, we do not care about these details, we only focus on the need to use and will them out, get [the interface] IDbSetInitializer specific implementation] [DbSetInitializer, call the class follows:

        public virtual void InitializeSets(DbContext context)
        {
            foreach (var setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null))
            {
                setInfo.Setter.SetClrValue(
                    context,
                    ((IDbSetCache)context).GetOrAddSet(_setSource, setInfo.ClrType));
            }
        }

Next Get Interface] [IDbSetFinder concrete realization [DbSetFinder] to find the existence Setter filtration properties DbSet (this point I do not have to explain), we do not care to find the details, each has its [DbSetProperty DbSet] property, so find after adding to the property and cached] [IDbSetCache in this respect DbSet lookup cache and had to get away, the next model to create all the data context.

Create a context model

The first is to acquire all the model data context to context is key to find the cached data model, if not create, or create a cache, as follows:

         public virtual IModel GetModel(
            DbContext context,
            IConventionSetBuilder conventionSetBuilder)
        {
            var cache = Dependencies.MemoryCache;
            var cacheKey = Dependencies.ModelCacheKeyFactory.Create(context);
            if (!cache.TryGetValue(cacheKey, out IModel model))
            {
                // Make sure OnModelCreating really only gets called once, since it may not be thread safe.
                lock (_syncObject)
                {
                    if (!cache.TryGetValue(cacheKey, out model))
                    {
                        model = CreateModel(context, conventionSetBuilder);
                        model = cache.Set(cacheKey, model, new MemoryCacheEntryOptions { Size = 100, Priority = CacheItemPriority.High });
                    }
                }
            }

            return model;
        }

Next to the link cache does not exist to create the model, the model is created primarily to do the following three things.

        protected  Virtual the IModel CreateModel ( 
            [NotNull] the DbContext context, 
            [NotNull] IConventionSetBuilder conventionSetBuilder) 
        { 
            Check.NotNull (context, NameOf (context)); 

            // build the default convention set by agreement distribution mechanism to deal with various conventions 
            var modelBuilder = new new the ModelBuilder (conventionSetBuilder.CreateConventionSet ()); 

            // custom processing method OnModelCreating configuration 
            Dependencies.ModelCustomizer.Customize (modelBuilder, context); 

            // after completion of model building, again makes the model data in the latest state in accordance with the agreed delivery mechanism 
            return modelBuilder. FinalizeModel (); 
        }

When instantiated ModelBuilder processing of each convention by convention distribution mechanism, specifically what do you do? Mainly to do the following three things

[1] to initialize the various conventions do some preparatory work, and add it to the corresponding conventions set to go.

[2] agreed to traverse a custom plug-in collection, agreed to modify the default correspondence and return a collection of the latest agreement.

[3] By agreement distribution mechanism, the process of acquiring a collection of the latest agreement obtained. 

The first [1] and [2] steps implemented by the code:

        public virtual ConventionSet CreateConventionSet()
        {
            var conventionSet = _conventionSetBuilder.CreateConventionSet();

            foreach (var plugin in _plugins)
            {
                conventionSet = plugin.ModifyConventions(conventionSet);
            }

            return conventionSet;
        }

EntityFramework Core built a concrete implementation of the agreed set of three to create a default provider interface [IProviderConventionSetBuilder], and [are] ProviderConventionSetBuilder used to build a database using the default provider for the collection of the convention, [RelationalConventionSetBuilder] used to build the model with the database mapping provider's default set of conventions, [SqlServerConventionSetBuilder] used to build a set of agreed default provider for SQL Server database, the three inheritance as follows:

    public class SqlServerConventionSetBuilder : RelationalConventionSetBuilder
    {
        var conventionSet = base.CreateConventionSet();
        ......
    }
    
    public abstract class RelationalConventionSetBuilder : ProviderConventionSetBuilder
    {
        public override ConventionSet CreateConventionSet()
        {
            var conventionSet = base.CreateConventionSet();
            
            var tableNameFromDbSetConvention = new TableNameFromDbSetConvention(Dependencies, RelationalDependencies);
            
            conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention(Dependencies, RelationalDependencies));
            
            conventionSet.EntityTypeAddedConventions.Add(tableNameFromDbSetConvention);

            ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, valueGenerationConvention);
            conventionSet.EntityTypeBaseTypeChangedConventions.Add(tableNameFromDbSetConvention);

            return conventionSet;
        }
    }
    
    public class ProviderConventionSetBuilder : IProviderConventionSetBuilder
    {  
        public virtual ConventionSet CreateConventionSet()
        {
      ...... } }

As surplus we have no use agreement has been removed, we see that the collection has been added to the [agreement] [RelationalTableAttributeConvention EntityTypeAddedConventions] and [] TableNameFromDbSetConvention agreed to table names, [for] TableNameFromDbSetConvention agreed to do the following in the construction of instantiation operating:

    public class TableNameFromDbSetConvention : IEntityTypeAddedConvention, IEntityTypeBaseTypeChangedConvention
    {
        private readonly IDictionary<Type, DbSetProperty> _sets;

        public TableNameFromDbSetConvention(
            [NotNull] ProviderConventionSetBuilderDependencies dependencies,
            [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
        {
            _sets = dependencies.SetFinder.CreateClrTypeDbSetMapping(dependencies.ContextType);

            Dependencies = dependencies;
        }
        ......
    }

We continue to look at how the above context is by obtaining DbSet property corresponds to the model of it?

        public static IDictionary<Type, DbSetProperty> CreateClrTypeDbSetMapping(
            [NotNull] this IDbSetFinder setFinder, [NotNull] Type contextType)
        {
            var sets = new Dictionary<Type, DbSetProperty>();
          
            var alreadySeen = new HashSet<Type>();
          
            foreach (var set in setFinder.FindSets(contextType))
            {
                if (!alreadySeen.Contains(set.ClrType))
                {
                    alreadySeen.Add(set.ClrType);
                    sets.Add(set.ClrType, set);
                }
                else
                {
                    sets.Remove(set.ClrType);
                }
            }
            return sets;
        }

As shown at initialization context we have all the properties DbSet context is cached, so the mapping model is to obtain the corresponding context attribute DbSet cached by the above method, is well understood, the following are also given when the source code debugger DbSet Blog corresponding attribute information.

Now we have to get all of the default set of conventions, the next instance of ModelBuilder, the default set of conventions as a parameter into account, as follows:

public class ModelBuilder : IInfrastructure<InternalModelBuilder>
{
     private readonly InternalModelBuilder _builder;

     public ModelBuilder([NotNull] ConventionSet conventions)
     {
         _builder = new InternalModelBuilder(new Model(conventions));
     }    
}

Then we continue to instantiate the Model, the incoming default set of conventions, began to instantiate the class and assign the agreed process the model through distribution mechanism agreed as follows:

public class Model : ConventionAnnotatable, IMutableModel, IConventionModel
{
    public Model([NotNull] ConventionSet conventions)
    {
        var dispatcher = new ConventionDispatcher(conventions);
        var builder = new InternalModelBuilder(this);
        ConventionDispatcher = dispatcher;
        Builder = builder;
        dispatcher.OnModelInitialized(builder);
    }
}

[Above] ConventionDispatcher class is for all stages of the model distributed processing (on subsequent re-distribution mechanism to deal with a detailed analysis of the individual through a blog), because the above two conventions we will table name] on [EntityTypeAddedConventions set, then we came down and agreed to distribute the mechanism agreed upon set of 12 agreed to traverse the default processing, as follows:

public override IConventionEntityTypeBuilder OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder)
{
    using (_dispatcher.DelayConventions())
    {
        _entityTypeBuilderConventionContext.ResetState(entityTypeBuilder);
        
        foreach (var entityTypeConvention in _conventionSet.EntityTypeAddedConventions)
        {
            entityTypeConvention.ProcessEntityTypeAdded(entityTypeBuilder, _entityTypeBuilderConventionContext);
        }
    }
    return entityTypeBuilder;
}

First, because the added [RelationalTableAttributeConvention] convention, so when traversing to [RelationalTableAttributeConvention] agreed, and went to deal with the specific implementation of the agreement, saying that white is to get the notes of the convention table name that is traversed characteristics, as follows:

public virtual void ProcessEntityTypeAdded(
    IConventionEntityTypeBuilder entityTypeBuilder,
    IConventionContext<IConventionEntityTypeBuilder> context)
{
    Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

    var attributes = type.GetTypeInfo().GetCustomAttributes<TAttribute>(true);

    foreach (var attribute in attributes)
    {
        ProcessEntityTypeAdded(entityTypeBuilder, attribute, context);
    }
}

[Method] ProcessEntityTypeAdded is provided to achieve a final specific model corresponds to a specific table name, as follows:

protected  the override  void ProcessEntityTypeAdded ( 
    IConventionEntityTypeBuilder entityTypeBuilder, 
    TableAttribute attribute, 
    IConventionContext <IConventionEntityTypeBuilder> context) 
{ 
       // If the definition of architectural features, for the model to add the schema name and table name characteristics 
    IF (! String .IsNullOrWhiteSpace (attribute.Schema)) 
    { 
        entityTypeBuilder. Totable (the attribute.name, attribute.Schema, fromDataAnnotation: to true ); 
    } 
    the else  IF (! String .IsNullOrWhiteSpace (the attribute.name)) 
    {   
       // If the table is not empty, the model name of the property name table defined in the table is added
        entityTypeBuilder.ToTable(attribute.Name, fromDataAnnotation: true);
    }
}

There are children's shoes asked, we only define the characteristics in the table schema name, then the above will not generate bug yet, used notes all know now provides a schema name in the table properties, then the table name must be provided, but the name of the table provided, schema name may not be provided, so the above processing logic did any wrong.

We continue to look for ToTable achieve the above-mentioned method in [RelationalEntityTypeBuilderExtensions] class as follows:

public static IConventionEntityTypeBuilder ToTable(
    [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false)
{
    if (!entityTypeBuilder.CanSetTable(name, fromDataAnnotation))
    {
        return null;
    }

    entityTypeBuilder.Metadata.SetTableName(name, fromDataAnnotation);
    return entityTypeBuilder;
}

我们看到该方法主要目的是判断该表名是否可设置,若不可设置则返回空,否则将设置该注解的名称作为模型的表名,我们看看上述CanSetTable又是如何判断是否可设置呢?

public static bool CanSetTable(
    [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false)
{
    Check.NullButNotEmpty(name, nameof(name));

    return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.TableName, name, fromDataAnnotation);
}

真是一层套一层,上述【RelationalAnnotationNames.TableName】是专为通过注解获取表名而定义的常量,其值为【Relational:TableName】,此时在注解字典中不存在该键,最终当然也就将模型的表特性名称作为模型的表名,如下:

public virtual bool CanSetAnnotation([NotNull] string name, [CanBeNull] object value, ConfigurationSource configurationSource)
{
    var existingAnnotation = Metadata.FindAnnotation(name);
    return existingAnnotation == null
        || CanSetAnnotationValue(existingAnnotation, value, configurationSource, canOverrideSameSource: true);
}

public virtual Annotation FindAnnotation([NotNull] string name)
{
    Check.NotEmpty(name, nameof(name));

    return _annotations == null
        ? null
        : _annotations.TryGetValue(name, out var annotation)
            ? annotation
            : null;
}

private static bool CanSetAnnotationValue(
    ConventionAnnotation annotation, object value, ConfigurationSource configurationSource, bool canOverrideSameSource)
{
    if (Equals(annotation.Value, value))
    {
        return true;
    }

    var existingConfigurationSource = annotation.GetConfigurationSource();
    return configurationSource.Overrides(existingConfigurationSource)
        && (configurationSource != existingConfigurationSource
            || canOverrideSameSource);
}

上述就是ToTable方法中调用第一个方法CanSetTable是否可设置表名的过程,主要就是在注解字典中查找注解名称为Relational:TableName是否已存在的过程,我们可以看到注解字典中不存在表名的注解名称,接下来调用第二个方法SetTableName方法去设置表名

public static void SetTableName(
    [NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false)
    => entityType.SetOrRemoveAnnotation(
        RelationalAnnotationNames.TableName,
        Check.NullButNotEmpty(name, nameof(name)),
        fromDataAnnotation);

接下来将是向注解字典中添加名为Relational:TableName,值为Blog2的注解,通过如下图监控可以清楚看到:

到目前为止,对于模型Blog已经通过注解即表特性设置了表名,接下来处理约定【TableNameFromDbSetConvention】,到底是覆盖还是跳过呢?我们还是一探其实现,如下:

public virtual void ProcessEntityTypeAdded(
    IConventionEntityTypeBuilder entityTypeBuilder,
    IConventionContext<IConventionEntityTypeBuilder> context)
{
    var entityType = entityTypeBuilder.Metadata;
    if (entityType.BaseType == null
        && entityType.ClrType != null
        && _sets.ContainsKey(entityType.ClrType))
    {
        entityTypeBuilder.ToTable(_sets[entityType.ClrType].Name);
    }
}

首先获取模型Blog的元数据,接下来判断其基类是否为空,该类型的原始类型不能为空,同时在其暴露的DbSet属性中包含该类型,很显然都满足条件,最后将我们上述对模型和DbSet属性进行了映射,所以设置其表名为Blog1,如下:

如上只是满足了条件进行设置,我们还要看看方法【ToTable】的具体实现才能最终下结论,此时依然会和注解判断逻辑一样,但是此时在注解字典中已存在键Relational:TableName,所以将跳过,如下:

好了,到此为止针对注解和DbSet对表名的设置已经讨论完毕,接下来我们进行到执行OnModelCreating方法即我们自定义的设置,如下代码:

Dependencies.ModelCustomizer.Customize(modelBuilder, context);
 
public virtual void Customize(ModelBuilder modelBuilder, DbContext context)
{
    context.OnModelCreating(modelBuilder);
}

此时将执行到我们对Blog自定义设置的表名Blog3,我们看看最终其ToTable方法直接跳过了CanSetTable方法,直接将参数名称赋值作为模型表名。

public static EntityTypeBuilder ToTable(
    [NotNull] this EntityTypeBuilder entityTypeBuilder,
    [CanBeNull] string name)
{
    entityTypeBuilder.Metadata.SetTableName(name);
    entityTypeBuilder.Metadata.RemoveAnnotation(RelationalAnnotationNames.ViewDefinition);

    return entityTypeBuilder;
}

到此为止对模型的初始化准备工作已经完成,接下来开始利用上下文进行操作,此时我们回到上一节利用上下文获取表名的方法,如下:

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

通过分析可知,无论是根据DbSet配置表名还是通过注解配置表名又或者是通过在OnModelCreating方法中自定义配置表名,最终在落地设置时,都统一以RelationalAnnotationNames.TableName即常量Relational:TableName为键设置表名值,所以上述若基类不存在就获取该表名常量的值,否则都未配置表名的话,才去以模型名称作为表名。

总结 

By this post and we considered on a detailed analysis of EntityFramework Core table name is considered uncertain, our next conclusion: EntityFramework Core for the table name configured priority is custom (OnModelCreating method)> Notes (Table characteristics) > DbSet property name> model name, we might be thinking why not register DbSet agreement, then the registry characteristic convention, taking mechanism covering it? But that is not true, here we just study the source code of the tip of the iceberg perhaps to consider other bar. If the exposure DbSet property, registered under the default name convention table named DbSet property, otherwise known as table model name, if the comment by setting the table name, this time in the context of exposure DbSet property will be ignored if a custom method by OnModelCreating configuration table, the final table will prevail custom name. So the question is, for the property in terms of whether it can be, and so on? Want to know only that you go and see the source code, step through the source code to verify that the entire logic can justify, read the blog if there is no clear statement or typos, two articles I spend more than a day's time, and I hope you have read this article little harvest, thank you.

Guess you like

Origin www.cnblogs.com/CreateMyself/p/12180250.html