奔跑的乌龟 發表於 2025-7-13 11:21:00

【EF Core】框架是如何识别实体类的属性和主键的

<p>在上一篇水文中,老周生动形象地解释了 DbContext 是如何识别实体 Set 的,大伙伴们可能会产生新的疑惑:实体是识别了,但,实体的属性或字段列表,它是怎么识别并映射给数据表的列的呢?</p>
<p>用过 EF 的人都知道(废话),其实默认情况下,实体类中只要不是静态的属性和字段都会被映射到数据表中,就算你不重写 DbContext 类的&nbsp;OnModelCreating 方法,EF 都能自动给你“造”个模型,这是啥机制。</p>
<p>老周知道,大伙们比魏公公还急,那就不绕关子了,先上结论。那是因为:<span style="text-decoration: underline"><strong>EF Core 中有一种类叫做约定(Conventions),或者翻译为“规范”也可以。这些约定实际上是一系列接口</strong></span>,但当然了,接口是不干活的,你得实现它(相信你们是学过面向老婆,哦不,是面向对象的)。为了不使大伙看得雨里云里雪里雾里,老周简单列一下常见的约定接口。这堆接口很多,也不要求你全都明白它们是什么,毕竟,咱们不会都用上的,除非你打算把 EF 的功能全部重写一遍(这样造轮子不太优雅)。</p>
<p><span style="color: rgba(0, 51, 102, 1)">1、IConvention:所有约定接口的 base,里面是空的,仅作为一个标志——你的类如果实现了它,那表示你是一个约定。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">2、IEntityTypeAddedConvention:当某个实体被添加到模型中,就会调用。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">3、IPropertyAddedConvention:为实体添加属性后,就会调用。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">4、IKeyAddedConvention:向实体添加主键后被调用。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">5、IKeyRemovedConvention:实体主键被删除后被调用。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">6、IPropertyRemovedConvention:从实体中删除某个属性后被调用。</span></p>
<p><span style="color: rgba(0, 51, 102, 1)">……</span></p>
<p>这时候你会想:咦?这尼马的怎么看着那么像事件回调啊?还真是呢,这些约定接口,只要你实现了并添加到框架中,当模型发生变更后就会被调用,这使得 EF Core 能够跟踪模型的改变,保证模型状态是最新的。注意了,这里跟踪的是模型的结构(如有几个实体,实体有哪些属性会映射到数据库,有几个主键等),不是数据。</p>
<p>在 EF Core 内部,在初始化时会添加一些“预制菜”以供框架自己食用。即预置的约定集合,它们由一个名为&nbsp;ProviderConventionSetBuilder 类负责创建。此类实现了&nbsp;IProviderConventionSetBuilder 接口,继而实现了&nbsp;CreateConventionSet 方法。</p>
<div class="cnblogs_code">
<pre>   <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span><span style="color: rgba(0, 0, 0, 1)"> ConventionSet CreateConventionSet()
   {
       </span><span style="color: rgba(0, 0, 255, 1)">var</span> conventionSet = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConventionSet();

       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ModelCleanupConvention(Dependencies));

       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NotMappedTypeAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OwnedAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ComplexTypeAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> KeylessAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> EntityTypeConfigurationAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NotMappedMemberAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BackingFieldAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConcurrencyCheckAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DatabaseGeneratedAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RequiredPropertyAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MaxLengthAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringLengthAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TimestampAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ForeignKeyAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UnicodeAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> PrecisionAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InversePropertyAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DeleteBehaviorAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NavigationBackingFieldAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RequiredNavigationAttributeConvention(Dependencies));

       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NavigationEagerLoadingConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DbSetFindingConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BaseTypeDiscoveryConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ManyToManyJoinEntityTypeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"><em><strong><span style="background-color: rgba(255, 255, 0, 1)"> PropertyDiscoveryConvention</span></strong></em>(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"><em><strong><span style="background-color: rgba(255, 255, 0, 1)"> KeyDiscoveryConvention</span></strong></em>(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ServicePropertyDiscoveryConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RelationshipDiscoveryConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ComplexPropertyDiscoveryConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ValueGenerationConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DiscriminatorConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CascadeDeleteConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ChangeTrackingStrategyConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConstructorBindingConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> KeyAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexAttributeConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ForeignKeyIndexConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ForeignKeyPropertyDiscoveryConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NonNullableReferencePropertyConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NonNullableNavigationConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BackingFieldConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> QueryFilterRewritingConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RuntimeModelConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ElementMappingConvention(Dependencies));
       conventionSet.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ElementTypeChangedConvention(Dependencies));

       </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> conventionSet;
   }</span></pre>
</div>
<p>好家伙,这么多。这里面有几位明星跟咱们今天的主题相关(高亮显示,被锥光灯对着那几位)。下面老详细但不啰嗦地介绍一下约定集合 ConventionSet。</p>
<p>这个类里面,为上面所列的接口(当然上面只列了常用的)各自分配一个 List&lt;T&gt; 类型的属性。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ConventionSet
{
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run to setup the initial model.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IModelInitializedConvention&gt; ModelInitializedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when model building is completed.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IModelFinalizingConvention&gt; ModelFinalizingConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when model validation is completed.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IModelFinalizedConvention&gt; ModelFinalizedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

   ……
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when a type is ignored.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;ITypeIgnoredConvention&gt; TypeIgnoredConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when an entity type is added to the model.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IEntityTypeAddedConvention&gt; EntityTypeAddedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when an entity type is removed.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IEntityTypeRemovedConvention&gt; EntityTypeRemovedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when a property is ignored.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IEntityTypeMemberIgnoredConvention&gt; EntityTypeMemberIgnoredConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

   ……

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when a primary key is changed.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IEntityTypePrimaryKeyChangedConvention&gt; EntityTypePrimaryKeyChangedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when an annotation is set or removed on an entity type.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IEntityTypeAnnotationChangedConvention&gt; EntityTypeAnnotationChangedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when a property is ignored.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IComplexTypeMemberIgnoredConvention&gt; ComplexTypeMemberIgnoredConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];

……

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">   Conventions to run when an annotation is changed on the element of a collection.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> List&lt;IElementTypeAnnotationChangedConvention&gt; ElementTypeAnnotationChangedConventions { <span style="color: rgba(0, 0, 255, 1)">get</span>; } =<span style="color: rgba(0, 0, 0, 1)"> [];
    ……
}</span></pre>
</div>
<p>太长了,老周省略了部分代码,反正各位知道这个规律就行。当调用 Add 方法向集合添加约定时,它会根据你的约定类所实现的接口来分类,添加到对应的 List 中。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Add(IConvention convention)
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实现了 IModelInitializedConvention接口的类,初始化模型时调用</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IModelInitializedConvention modelInitializedConvention)
      {
            ModelInitializedConventions.Add(modelInitializedConvention);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实现了IModelFinalizingConvention接口的类,在模型初始化之前调用</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IModelFinalizingConvention modelFinalizingConvention)
      {
            ModelFinalizingConventions.Add(modelFinalizingConvention);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化之后调用</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IModelFinalizedConvention modelFinalizedConvention)
      {
            ModelFinalizedConventions.Add(modelFinalizedConvention);
      }
</span><span style="color: rgba(0, 0, 0, 1)">       ……

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实体类型被添加到模型后调用</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IEntityTypeAddedConvention entityTypeAddedConvention)
      {
            EntityTypeAddedConventions.Add(entityTypeAddedConvention);
      }

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实体从模型中删除后调用</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IEntityTypeRemovedConvention entityTypeRemovedConvention)
      {
            EntityTypeRemovedConventions.Add(entityTypeRemovedConvention);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (convention <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IEntityTypeMemberIgnoredConvention entityTypeMemberIgnoredConvention)
      {
            EntityTypeMemberIgnoredConventions.Add(entityTypeMemberIgnoredConvention);
      }

   ……
}</span></pre>
</div>
<p>不管是“预制”的约定,还是咱们自己定义的,都可以添加到此集合中。</p>
<p>&nbsp;</p>
<p>现在约定集合有了,怎么让它运作起来呢?EF Core 整了个调度器——&nbsp;ConventionDispatcher,该类中公开一系列 OnXXXX 方法,对应着模型的各种行为。比如,OnModelInitialized 方法在模型完成初始化后被 Model 类调用,此方法会调用约定集合中所有实现了&nbsp;IModelInitializedConvention 接口的约定类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> _modelBuilderConventionContext.ResetState(modelBuilder);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> modelConvention <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> conventionSet.<span style="background-color: rgba(255, 255, 0, 1)">ModelInitializedConventions</span>)
{
   modelConvention.<span style="background-color: rgba(255, 255, 0, 1)">ProcessModelInitialized</span>(modelBuilder, _modelBuilderConventionContext);
   </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (_modelBuilderConventionContext.ShouldStopProcessing())
   {
         </span><span style="color: rgba(0, 0, 255, 1)">return</span> _modelBuilderConventionContext.Result!<span style="color: rgba(0, 0, 0, 1)">;
   }
}</span></pre>
</div>
<p>当然,这里头很复杂,ConventionDispatcher 这些方法并非直接实现,而是嵌套了几个内部类,这些类实现&nbsp;ConventionScope 抽象类。即&nbsp;ImmediateConventionScope 和&nbsp;DelayedConventionScope。这些类同样公开了 OnXXXX 方法。也就是说,ConventionDispatcher 类的 OnXXX 方法调用了这两个嵌套类的 OnXXXX 方法。</p>
<p>前文提到,OnModelInitialized 方法中通过 foreach 循环调用所有实现了&nbsp;IModelInitializedConvention 接口的约定类。而&nbsp;DbSetFindingConvention 类正是实现了该接口,在&nbsp;ProcessModelInitialized 方法的实现中,通过&nbsp;SetFinder 对象,进而将 DbContext 子类的 DbSet&lt;T&gt; 类型属性所对应的实体类添加到 Model 中。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ProcessModelInitialized(
      IConventionModelBuilder modelBuilder,
      IConventionContext</span>&lt;IConventionModelBuilder&gt;<span style="color: rgba(0, 0, 0, 1)"> context)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> setInfo <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"><em><strong> Dependencies.SetFinder.FindSets</strong></em></span>(Dependencies.ContextType))
      {
            <span style="background-color: rgba(255, 255, 153, 1)">modelBuilder.Entity(setInfo.Type, fromDataAnnotation: </span></span><span style="color: rgba(0, 0, 255, 1); background-color: rgba(255, 255, 153, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 153, 1)">)</span>;
      }
    }</span></pre>
</div>
<p>注意上面的&nbsp;Dependencies.SetFinder.FindSets,咱们看看它里面是如何获得实体类型信息的。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> IReadOnlyList&lt;DbSetProperty&gt;<span style="color: rgba(0, 0, 0, 1)"> FindSets(Type contextType)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> _cache.GetOrAdd(contextType, FindSetsNonCached);

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> DbSetProperty[] FindSetsNonCached(Type contextType)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> factory =<span style="color: rgba(0, 0, 0, 1)"> ClrPropertySetterFactory.Instance;

      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> contextType.GetRuntimeProperties()
            .Where(
                <strong><span style="background-color: rgba(255, 255, 0, 1)">p </span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)">=&gt; !p.IsStatic()
</span></strong>&amp;&amp; !&amp;&amp; p.DeclaringType != typeof&amp;&amp;&amp;&amp; p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet&lt;&gt;))
            .OrderBy(p =&gt; p.Name)
            .Select(
                p =&gt; new DbSetProperty(
                  p.Name,
                  p.PropertyType.GenericTypeArguments.Single(),
                  p.SetMethod == null ? null : factory.Create(p)))
            .ToArray();
    }</pre>
</div>
<p>其实就是从 DbContext 的子类中查找符合以下条件的属性:</p>
<p><span style="color: rgba(0, 0, 128, 1)">1、非静态成员;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">2、非索引;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">3、属性的类型是泛型;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">4、这个泛型类是&nbsp;DbSet&lt;&gt;。</span></p>
<p>老周就不继续套了,不然大伙们会头晕的,这里老周直接简单说一下这个调用链:</p>
<p><span style="color: rgba(255, 102, 0, 1)">--&gt; DbContext以及数据库提供者初始化 </span></p>
<p><span style="color: rgba(255, 102, 0, 1)">--&gt;&nbsp;DbContextServices从服务容器中被提取 </span></p>
<p><span style="color: rgba(255, 102, 0, 1)">--&gt; 访问 DbContextServices的 Model 或&nbsp;DesignTimeModel 属性以获得 Model 对象 </span></p>
<p><span style="color: rgba(255, 102, 0, 1)">--&gt; 如果 Model 未实例化则调用&nbsp;CreateModel 方法 </span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; --&gt; 先从 DbContextOptions 中找 Model(实际通过 CoreOptionsExtension 类的 Model 属性获取);</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; --&gt; DbContextOptions 中未找到 Model,则从 DbContext 子类所在的程序集中查找&nbsp;DbContextModelAttribute 特性,此特性应用在程序集上,用于描述自定义 Model 的类型(也就是说你可以自己实现 IModel 接口,把整个 EF Core 框架的模型管理机制替换掉);</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; --&gt; 如果在 DbContext 子类所在的程序集还是找不到 Model,那就用&nbsp;ModelSource 类去找;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; --&gt; ModelSource 先从缓存的对象中查查有没有现成的 Model;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; --&gt; 缓存中找不到现存的 Model,认栽了,那就 new 一个;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--&gt; new 一个&nbsp;ModelConfigurationBuilder 实例;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--&gt; 调用 DbContext 子类的&nbsp;ConfigureConventions 方法。这个方法是虚的,默认是空。你在继承 DbContext 类时可以重写此方法,添加自定义的约定类;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--&gt; new 一个 ModelBuilder 实例,调用 DbContext 子类的&nbsp;OnModelCreating 方法。你在继承 DbContext 类时可以重写此方法,自己去定义模型结构。这个相信大伙伴很常用也很熟悉的套路了;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--&gt; 最后通过 ModelBuilder.Model 属性就能获取到 Model 实例了。</span></p>
<p>在以上过程中,各种预置的约定类会被调用,当然包括&nbsp;DbSetFindingConvention 类啦。</p>
<p>&nbsp;</p>
<p>现在,大伙大概知道 DbContext 公开 DbSet&lt;T&gt;,到这些 DbSet 被添加到模型的原理。既然实体类型是通过&nbsp;DbSetFindingConvention 约定类添加到模型中的,那么咱们可以推测到,实体类的属性也是通过约定自动添加到模型中的,对应的约定就是&nbsp;PropertyDiscoveryConvention 类。</p>
<p>啊,What the KAO!前面讲了这么多铺垫的话,终于轮到主角出场了。PropertyDiscoveryConvention 类的默认实现中,只要实体类中非静态的属性和字段都会被添加到模型中,从而会被映射到数据库中。</p>
<p>如果我们不希望某个属性被映射,最简单的方法是在这个属性(或字段,甚至整个实体类)上应用&nbsp;<span class="hljs-title">NotMappedAttribute 就行了。不过,如果被排除的属性是具有共性的呢,总不能你每个实体类中都放一次 NotMapped 特性吧。为了好理解,老周下面用示例来说明。假设咱们的项目有这么一条规则:实体类中不管是属性还是字段,凡是带下画线开头的都不能映射到数据库中,即,如 _What、__What 之类命名的都被排除。这种情况下,一个个地做模型配置会很麻烦,就得用上约定了,只要向模型添加新实体,约定就会自动运行,排除下画线开头的成员。</span></p>
<p><span class="hljs-title">咱们不需要全新造轮子,所以,最好的方案是从&nbsp;PropertyDiscoveryConvention 派生。PropertyDiscoveryConvention 类公开一个虚方法叫&nbsp;DiscoverPrimitiveProperties,分析属性(或字段)时否要添加到模型的逻辑都在该方法中实现的。所以,老周这里不用官方文档的方法(官方示例重写了多个方法,并且有的代码是从基类拷贝过去的),而是直接重写&nbsp;DiscoverPrimitiveProperties 方法,找到下画线开头的属性,然后忽略掉即可。</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CustPropertyDiscoveryConvention : PropertyDiscoveryConvention
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注意,基类构造函数需要 ProviderConventionSetBuilderDependencies 类型的参数,所以我们要定义这个构造函数</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CustPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies deps)
            :</span><span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">(deps)
      {
      }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> DiscoverPrimitiveProperties(IConventionTypeBaseBuilder structuralTypeBuilder, IConventionContext context)
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取CLR类型</span>
            Type clrtype =<span style="color: rgba(0, 0, 0, 1)"> structuralTypeBuilder.Metadata.ClrType;
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 找出带“_”开头的属性</span>
            <span style="color: rgba(0, 0, 255, 1)">var</span> props =<span style="color: rgba(0, 0, 0, 1)"> clrtype.GetRuntimeProperties()
                              .<strong><span style="background-color: rgba(255, 255, 0, 1)">Where(p </span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)">=&gt; p.Name.StartsWith(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">_</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">))</span></strong>;
            </span><span style="color: rgba(0, 0, 255, 1)">foreach</span>(PropertyInfo p <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> props)
            {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这些属性忽略</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 再调用基类成员,执行“预制”的约定,这时,被忽略的成员不再添加到模型</span>
            <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.DiscoverPrimitiveProperties(structuralTypeBuilder, context);
      }
    }</span></pre>
</div>
<p>然后,咱们定义一个实体类来测试一下。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Person
    {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span>? Age { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? _WhatIsThis { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
    }</span></pre>
</div>
<p>注意那个 _WhatIsThis 属性,按照本例规则,它无缘映射到数据库。</p>
<p>从 DbContext 类派生一个类。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext : DbContext
    {
      </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
      <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 用户访问实体
      </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span> DbSet&lt;Person&gt; Persons {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置数据库连接</span>
            optionsBuilder.UseSqlServer(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Demo;Integrated Security=True;Trust Server Certificate=True</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
      {
            configurationBuilder.<strong><span style="background-color: rgba(255, 255, 0, 1)">Conventions.Replace</span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)">&lt;PropertyDiscoveryConvention&gt;</span></strong>(sp =&gt;<span style="color: rgba(0, 0, 0, 1)">
            {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1、获取 ProviderConventionSetBuilderDependencies 服务实例,因为构造函数需要它</span>
                <span style="color: rgba(0, 0, 255, 1)">var</span> deps = sp.GetRequiredService&lt;ProviderConventionSetBuilderDependencies&gt;<span style="color: rgba(0, 0, 0, 1)">();
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2、返回自定义属性发现约定的实例</span>
                <strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)"> CustPropertyDiscoveryConvention(deps)</span></strong>;
            });
      }
    }</span></pre>
</div>
<p>这里老周调用了 Replace 方法,注意泛型参数一定要指定基类&nbsp;PropertyDiscoveryConvention,因为 EF Core 默认注册的类型是&nbsp;PropertyDiscoveryConvention,而不是咱们自定义的&nbsp;CustPropertyDiscoveryConvention。这里就是把默认的约定替换成咱们自己的。另外,你也可能不调用 Replace 方法,而是 Add 方法直接添加一个新的约定。这样做也是可以的,只不过&nbsp;PropertyDiscoveryConvention 的&nbsp;DiscoverPrimitiveProperties 方法会处理两次。其实影响也不大。</p>
<p><span class="hljs-title">最后,咱们实例化数据库上下文,并创建一个数据库。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> dc = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext())
{
      dc.Database.EnsureCreated();
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出模型的调试信息</span>
<span style="color: rgba(0, 0, 0, 1)">      Console.WriteLine(dc.Model.ToDebugString(MetadataDebugStringOptions.ShortDefault));
}</span></pre>
</div>
<p>运行程序代码,看到输出的模型信息中未包含 _WhatIsThis 属性。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202507/367389-20250713105310080-1532079295.png" alt="" width="377" height="90" loading="lazy"></p>
<p>再看看创建的数据库,表中也是没有下画线开头的列。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202507/367389-20250713105432798-1583226464.png" alt="" width="268" height="153" loading="lazy"></p>
<p>这就表明咱们自己的约定类被成功执行了。</p>
<p>&nbsp;</p>
<p>咱们知道,EF Core 不仅会自动发现实体的属性,同时也会根据属性的命名自动识别主键。如你的实体类名为 Song,如果你的实体中有个属性叫 Id,或叫 SongId,类型是Guid、int 之类的类型,那这个属性会被自动标记为主键。</p>
<p>有了上面的认知,咱们也很快猜出来,还是预置约定干的活。对的,它叫&nbsp;KeyDiscoveryConvention。如果你要对自动发现主键做定制化处理,为了便于批量应用于实体,也可以从&nbsp;KeyDiscoveryConvention 派生一个类来搞搞,然后替换或添加到约定集合中即可。就像上面的示例一样,如法炮制,套路都一样的。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/tcjiaan/p/18980412
頁: [1]
查看完整版本: 【EF Core】框架是如何识别实体类的属性和主键的