【EF Core】聊聊“复合”属性
<p>复合(或复杂)属性,即 Complex Property,怎么理解呢?这是相对于常见的基础类型,比如 string、int、byte、long 等类型的属性就是基础类型值,而由多个基础类型构成的类型就是复合类型(当然,复合类型的属性也可以其他复合类型,这里就不套娃了)。</p><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)"> 大餐
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="background-color: rgba(255, 255, 0, 1)">MiFan 饭</span> { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 复合</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="background-color: rgba(255, 255, 0, 1)">Tang 汤</span> { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 复合</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="background-color: rgba(255, 255, 0, 1)">CaiRou 主菜</span> { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 复合</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)">class</span><span style="color: rgba(0, 0, 0, 1)"> MiFan
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> 原料 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用什么煮的</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> 单位 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 千克</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">float</span> 量 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 吃多少饭</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)">class</span><span style="color: rgba(0, 0, 0, 1)"> Tang
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> 主材 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用什么煲的汤</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? 配材 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配哪些辅料</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">float</span> 水用量 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用多少水</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> DateTime 用时 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 煮了多久</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)">class</span><span style="color: rgba(0, 0, 0, 1)"> CaiRou
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> 分类 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 肉还是青菜</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">double</span>? 分量 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 吃多少</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span>? 是否混合 { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是菜肉混合还是吃素的</span>
}</pre>
</div>
<p>其中,饭、汤、主菜几个属性都引用了其他的类,就是复合类型。这时候你会发现:咦,这怎么看着像导航属性?</p>
<p>是的,很像,但只是很像而已,实际上是有区别的。老周总结如下:</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、由于导航属性引用的是实体,所以它可以是单个实例或集合类型。而复合类型没有映射单独的表,而是被拆解为主表的字段,所以它引用的对象不能是集合(至少目前是不支持的)。</span></p>
<p>那么,应该在什么时候,或什么场景下使用复合属性(而不是导航属性)?一句话斯基:<span style="color: rgba(255, 0, 0, 1)"><strong>你只想用一个类来组织一些信息,但不希望它成为实体的时候</strong></span>。有大伙伴会质问老周:你不是废话吗。这不是废话,本来就是这个用途的。</p>
<p>例如,一个比 SARS 还典型的应用场景——我的产品订单信息中会包含订货者的信息,但这个信息用一个字段描述显然不够,因为它包含联系人是谁、什么电话号码、住在哪里。当然你也可以单独用一个表来存放联系人信息,如果这样它就是实体类了,就要用导航属性。可是,有些时候是没必要单独去存储的(比如,订你货的人或单位不是常客,何必多建一张表呢)。</p>
<p>光说不练等于白说,咱们就动手试试。</p>
<p>假如我有个 Person 实体类,它可以表示任意人员(员工、学生、罪犯、外星人),其中,Contact 属性表示联系方式。这个信息量超出单个字段的描述范围(用XML或JSON字段的当我没说),于是,我另外定义了一个 ContactInfo 类存放联系信息。</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)"><span style="background-color: rgba(255, 255, 0, 1)"> ContactInfo</span>
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Phone { <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)">string</span>? Email { <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> City { <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)">string</span> Town { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</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)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Street { <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)">class</span><span style="color: rgba(0, 0, 0, 1)"> Person
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Guid 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> FirstName { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</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)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> LastName { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</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)">public</span> DateOnly? RegistDate { <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="background-color: rgba(255, 255, 0, 1)"><strong>ContactInfo Contact</strong></span> { <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)">new</span><span style="color: rgba(0, 0, 0, 1)">();
}</span></pre>
</div>
<p>目前来说,EF Core 是不允许复合属性引用 null 值的,即不能声明为 ContactInfo?。但复合类型中的属性是可以为 null 的。</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)"> MyContext : DbContext
{
</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)
{
optionsBuilder.UseSqlServer(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Server=(localdb)\\MsSQLLocalDB; Database=person_db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
)
.<strong><em><span style="background-color: rgba(255, 255, 0, 1)">LogTo(m </span></em></strong></span><strong><em><span style="background-color: rgba(255, 255, 0, 1)">=> Console.WriteLine(m))</span></em></strong>;<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)"> }
</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)"> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity</span><Person>(eb =><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>
eb.ToTable(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_persons</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
.HasKey(x </span>=> x.ID).HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</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>
eb.Property(x => x.FirstName).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">);
eb.Property(nameof(Person.LastName)).HasMaxLength(</span><span style="color: rgba(128, 0, 128, 1)">25</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>
<em><strong><span style="background-color: rgba(255, 255, 0, 1)">eb.ComplexProperty(x =></span></strong></em><span style="color: rgba(0, 0, 0, 1)"><em><strong><span style="background-color: rgba(255, 255, 0, 1)"> x.Contact);</span></strong></em>
});
}
}</span></pre>
</div>
<p>A、LogTo 方法配置一个委托,接收一个字符串对象,它就是日志消息。这是配置日志输出最简单的用法;如果不配置应用程序就不会收到日志呗。</p>
<p>B、重写 OnModelCreating 方法,构建实体模型。Person 实体在映射时具有以下特征:</p>
<p><span style="color: rgba(0, 0, 128, 1)"> 1、数据表名为 tb_persons;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)"> 2、主键的名称为 PK_Person;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)"> 3、表示姓和名的字段设置长度限制;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)"> 4、Contact 属性配置为复合属性。注意,EF Core 的“预置”约定是发现不了复合属性的,得手动去配置。约定会识别为导航属性,但 ContactInfo 类没有被配置为实体,运行后会发生异常。</span></p>
<p>有大伙伴可能不解了:FK,你这 Context 类不对劲,没有公开 DbSet<> 类型的属性,能访问实体吗?EF 能找到实体吗?</p>
<p><span style="color: rgba(0, 0, 128, 1)">1、公开 DbSet<> 的属性是为了让预置约定自动添加实体集合,但,我不公开也不影响使用,只要调用 Set 方法,DbContext 也会添加实体集合的;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">2、DbSet<> 实际上是提供一个窗口让我们执行查询,它不负责构建模型。真正用来建模的是你重写的 ModelBuilder,只要你调用 Entity 方法,它就知道你要把 Person 类作为实体添加到模型中了(如你所见,并未为 ContactInfo 类调用 Entity 方法,表明它不是实体类)。</span></p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>注:这个项目要引用 Microsoft.EntityFrameworkCore.SqlServer 包,我用的是 SQL Server 数据库(LocalDB),你也可以用其他数据库,就要引用对应的包。操作方法请参考巴巴尔星人的博客。</strong></span></p>
<p>前面提了,EF 的约定不会发现复合属性,所以你<strong><span style="color: rgba(255, 102, 0, 1)">要调用 ComplexProperty 方法明确告诉 EF 这是个复合属性</span></strong>。</p>
<p>为了方便演示,本示例每次运行都会先删除数据库,再创建一个新——每次都是全新的。</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> ctx = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyContext())
{
</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)"> ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果空的,添加点料</span>
DbSet<Person> persons = ctx.Set<Person><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (persons.Any() == <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
{
persons.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Person
{
FirstName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
LastName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
RegistDate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DateOnly(<span style="color: rgba(128, 0, 128, 1)">2024</span>, <span style="color: rgba(128, 0, 128, 1)">2</span>, <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">),
Contact </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ContactInfo
{
Phone </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">18808828811</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
Email </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">axax@ktv.edu.cn</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
City </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Town </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Street </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">番茄路狗头街333号</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
});
persons.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Person
{
FirstName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
LastName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
RegistDate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DateOnly(<span style="color: rgba(128, 0, 128, 1)">2023</span>, <span style="color: rgba(128, 0, 128, 1)">8</span>, <span style="color: rgba(128, 0, 128, 1)">25</span><span style="color: rgba(0, 0, 0, 1)">),
Contact </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">()
{
Phone </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">18925531100</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
Email </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">GreenMan123@tbcg.net</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
City </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Town </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Street </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">迷路1008号</span><span style="color: rgba(128, 0, 0, 1)">"</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, 0, 1)"> ctx.SaveChanges();
}
}</span></pre>
</div>
<p>上面代码没什么要点,如果数据库已存在,删之;若不存在,建之。新数据库是没数据的,所以插入些新记录。</p>
<p>由于咱们在配置上下文时已经开启了日志功能,运行后咱们看看 EF 是如何对待复合属性的(主要看生成的 SQL 语句)。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">tb_persons</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">uniqueidentifier</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">FirstName</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">10</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">LastName</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">25</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">RegistDate</span><span style="color: rgba(255, 0, 0, 1)">]</span> date <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Contact_City</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span></span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Contact_Email</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Contact_Phone</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span></span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Contact_Street</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Contact_Town</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span></span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">PK_Person</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)">)
);</span></pre>
</div>
<p>这下你是否已明白,为什么复合属性的类型不需要主键了吧,因为它不会单独成表,只不过是把属性成员分散到 tb_person 表的字段列表中。所以,在数据库层面,复合对象其实是主表的一部分;而在实体层面,将其作为一个整体,我们在写.NET 代码时才有面向对象的感觉。这是为咱们程序服务的。</p>
<p>我们还得关心一个问题:复合属性是否支持更改跟踪——能不能自动生成更新的 SQL。咱们试一下修改其中一条记录的复合属性所引用的 ContactInfo 对象的属性值。</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> c = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyContext())
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 修改</span>
Person? firstOne = c.Set<Person><span style="color: rgba(0, 0, 0, 1)">().FirstOrDefault();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (firstOne != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
firstOne.Contact.Town </span>= <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 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, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">Console.WriteLine(c.ChangeTracker.ToDebugString())</span></strong>;
}
}</span></pre>
</div>
<p>上述代码把第一条记录的联系人信息更新了,但没有调用 SaveChanges 方法,ToDebugString 方法可以输出哪些更改被跟踪到了。也就是说,这次修改实际上不会更新到数据库的。输出内容如下:</p>
<div class="cnblogs_code">
<pre>Person {ID: f9a372f8-849b-420b-8cb3-<span style="color: rgba(0, 0, 0, 1)">08ddd7ec9422} Unchanged
ID: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">f9a372f8-849b-420b-8cb3-08ddd7ec9422</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> PK
FirstName: </span><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 style="color: rgba(0, 0, 0, 1)">
LastName: </span><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 style="color: rgba(0, 0, 0, 1)">
RegistDate: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">2024/2/10</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Contact (Complex: ContactInfo)
City: </span><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 style="color: rgba(0, 0, 0, 1)">
Email: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">axax@ktv.edu.cn</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Phone: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">18808828811</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Street: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">番茄路狗头街333号</span><span style="color: rgba(128, 0, 0, 1)">'</span>
<em><span style="background-color: rgba(0, 255, 0, 1)"><span style="color: rgba(0, 0, 0, 1)">Town: </span><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> Originally <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></em></pre>
</div>
<p>它已经跟踪到了 Town 属性的变更。</p>
<p>现在,咱们把更新正式提交到数据库,在更新后从数据库重新查询一下。</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> c = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyContext())
{
Console.WriteLine(</span><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 style="color: rgba(0, 0, 0, 1)">);
<span style="background-color: rgba(0, 255, 0, 1)">PrintInfo(c.Set</span></span><span style="background-color: rgba(0, 255, 0, 1)"><Person><span style="color: rgba(0, 0, 0, 1)">().AsEnumerable());
</span></span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 修改</span>
Person? firstOne = c.Set<Person><span style="color: rgba(0, 0, 0, 1)">().FirstOrDefault();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (firstOne != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
firstOne.Contact.Town </span>= <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 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, 0, 1)"> c.SaveChanges();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Console.WriteLine(c.ChangeTracker.ToDebugString());</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> c = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyContext())
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n修改后:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
<span style="background-color: rgba(0, 255, 0, 1)">PrintInfo(c.Set</span></span><span style="background-color: rgba(0, 255, 0, 1)"><Person></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(0, 255, 0, 1)">().AsEnumerable());</span>
}
</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)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> PrintInfo(IEnumerable<Person><span style="color: rgba(0, 0, 0, 1)"> persons)
{
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> p <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> persons)
{
Console.Write(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">姓{0}名{1},来自{2}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, p.FirstName, p.LastName, p.Contact.City + p.Contact.Town +<span style="color: rgba(0, 0, 0, 1)"> p.Contact.Street);
Console.Write(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p>更新前后各打印一次,效果如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">修改前:
姓洪名水,来自天津市<span style="background-color: rgba(255, 255, 0, 1)">黄牛镇</span>番茄路狗头街333号
姓高名大桂,来自成都市金龙镇迷路1008号
修改后:
姓洪名水,来自天津市<span style="background-color: rgba(255, 255, 0, 1)">石头镇</span>番茄路狗头街333号
姓高名大桂,来自成都市金龙镇迷路1008号</span></pre>
</div>
<p> </p>
<p>接下来,老周再介绍一种特殊情况,目前来说是不能用复合属性处理的。先介绍一下实体关系:</p>
<p><span style="color: rgba(0, 0, 255, 1)">有一个 Album 实体,它表示一张 CD 专辑。其中,它有个 Tracks 属性表示本专辑包含的曲目,这是个集合,其中每个元素是 Track 对象;</span></p>
<p><span style="color: rgba(0, 0, 255, 1)">Track 实体表示单首曲子的信息,其中,它有个 Artist 属性,表示表演/演唱这首曲子的人(歌手),该属性的类型是 Performer 类。</span></p>
<p>总结一下就是:Album 类的Tracks 属性中包含 Track 类,而 Track 类的 Artist 属性引用了 Performer 类。</p>
<p>实体定义如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<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)"><strong><span style="background-color: rgba(0, 204, 255, 1)"> Performer</span></strong>
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Alias {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Gender {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? StageName { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> DateOnly? Birthday {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Brief {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<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)"><strong><span style="background-color: rgba(255, 255, 153, 1)"> Track</span></strong>
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 轨道ID
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TrackIndex {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> TrackName { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="background-color: rgba(0, 204, 255, 1)"><strong>Performer Artist</strong></span> { <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)">new</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)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span>? Duration { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Comment {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<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)"> Album
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 专辑ID
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Title { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Year {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Style {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TrackNum {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <strong><span style="background-color: rgba(255, 255, 153, 1)">List<Track> Tracks</span></strong> { <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)">new</span><span style="color: rgba(0, 0, 0, 1)">();
}</span></pre>
</div>
<p>如果你不想给歌手/表演者实体独立建表,那此情况可以使用“从属实体”解决,即 Album 管着 Track,Track 管着 Performer。</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)"> TestContext : DbContext
{
</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)">public</span> TestContext(DbContextOptions<TestContext> options) : <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">(options)
{
}
</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)"> OnModelCreating(ModelBuilder modelBuilder)
{</span>
modelBuilder.Entity<Album>(ae =><span style="color: rgba(0, 0, 0, 1)">
{
ae.HasKey(a </span>=><span style="color: rgba(0, 0, 0, 1)"> a.ID);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一层:Album --> Track,一管多</span>
<span style="background-color: rgba(255, 255, 0, 1)">ae.OwnsMany(a => a.Tracks, obuilder =></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)"> 第二层:Track --> Performer,一管一</span>
<span style="background-color: rgba(255, 255, 0, 1)">obuilder.OwnsOne(x =></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"> x.Artist);</span>
});
});
}
</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.Conventions.Remove</span><ForeignKeyIndexConvention><span style="color: rgba(0, 0, 0, 1)">();
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DbSet<Album> Albums { <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>
<div>configurationBuilder.Conventions.Remove<ForeignKeyIndexConvention>() 是把 ForeignKeyIndexConvention 约定删除,因为 Album 与 Track 之间会产生外键,而这个约定默认为外键创建索引。把这个约定从预置列表中删掉,表明不会自动创建索引。</div>
<div>然后咱们看一下 EF 是如何创建表的。</div>
<div>
<div class="cnblogs_code">
<pre> <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> ctxOptions = <span style="color: rgba(0, 0, 255, 1)">new</span> DbContextOptionsBuilder<TestContext><span style="color: rgba(0, 0, 0, 1)">()
.UseSqlServer(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">server=(localdb)\\MSSQLLOCALDB;</span>
database=db_songs<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">)</span>
.LogTo(msg =><span style="color: rgba(0, 0, 0, 1)"> Console.WriteLine(msg))
.Options;
</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)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestContext(ctxOptions))
{
</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)"> context.Database.EnsureDeleted();
context.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, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">context.Albums.Any())
{
context.Albums.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Album
{
Title </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Year </span>= <span style="color: rgba(128, 0, 128, 1)">2025</span><span style="color: rgba(0, 0, 0, 1)">,
TrackNum </span>= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">,
Style </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Tracks </span>=<span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Track{
TrackIndex </span>= <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">,
TrackName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Duration </span>= <span style="color: rgba(128, 0, 128, 1)">5556329L</span><span style="color: rgba(0, 0, 0, 1)">,
Artist </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Performer{
Name </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Alias </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Age </span>= <span style="color: rgba(128, 0, 128, 1)">15</span><span style="color: rgba(0, 0, 0, 1)">,
Birthday </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DateOnly(<span style="color: rgba(128, 0, 128, 1)">2012</span>, <span style="color: rgba(128, 0, 128, 1)">7</span>, <span style="color: rgba(128, 0, 128, 1)">12</span><span style="color: rgba(0, 0, 0, 1)">),
Gender </span>= <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 style="color: rgba(0, 0, 0, 1)">,
StageName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Brief </span>= <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 style="color: rgba(0, 0, 0, 1)">
}
},
</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Track{
TrackIndex</span>= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">,
TrackName </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Duration </span>= <span style="color: rgba(128, 0, 128, 1)">652224L</span><span style="color: rgba(0, 0, 0, 1)">,
Artist </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Performer{
Name </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Age </span>= <span style="color: rgba(128, 0, 128, 1)">16</span><span style="color: rgba(0, 0, 0, 1)">,
Alias </span>= <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 style="color: rgba(0, 0, 0, 1)">,
Birthday </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DateOnly(<span style="color: rgba(128, 0, 128, 1)">2007</span>, <span style="color: rgba(128, 0, 128, 1)">8</span>, <span style="color: rgba(128, 0, 128, 1)">31</span><span style="color: rgba(0, 0, 0, 1)">),
Gender </span>= <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 style="color: rgba(0, 0, 0, 1)">,
StageName </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">天下第一的fufu殿下</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
Brief </span>= <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 style="color: rgba(0, 0, 0, 1)">
}
}
]
});
context.SaveChanges();
}
}
</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)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> ctx = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestContext(ctxOptions))
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> q = <span style="color: rgba(0, 0, 255, 1)">from</span> a <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> ctx.Albums
</span><span style="color: rgba(0, 0, 255, 1)">select</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">
{
a.Year,
a.Title,
Tracks </span>= (<span style="color: rgba(0, 0, 255, 1)">from</span> k <span style="color: rgba(0, 0, 255, 1)">in</span> a.Tracks <span style="color: rgba(0, 0, 255, 1)">select</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> { k.
TrackIndex, k.TrackName, Artist </span>=<span style="color: rgba(0, 0, 0, 1)"> k.Artist.
Name })
};
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span>(<span style="color: rgba(0, 0, 255, 1)">var</span> obj <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> q)
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{obj.Year} - {obj.Title}</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)">foreach</span>(<span style="color: rgba(0, 0, 255, 1)">var</span> t <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> obj.Tracks)
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\t{t.TrackIndex}. {t.TrackName}\t</span>
演唱: {t.Artist}<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">);</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}
}</span></pre>
</div>
<p>运行程序,看到创建表的 SQL 语句:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(255, 0, 255, 1)">IDENTITY</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Title</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Year</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Style</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">TrackNum</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">PK_Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)">)
);
</span><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Track</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(255, 0, 255, 1)">IDENTITY</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">AlbumID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">TrackIndex</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">TrackName</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Name</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Alias</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Gender</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_StageName</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Age</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Birthday</span><span style="color: rgba(255, 0, 0, 1)">]</span> date <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 204, 153, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Artist_Brief</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Duration</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">bigint</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Comment</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">PK_Track</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">AlbumID</span><span style="color: rgba(255, 0, 0, 1)">]</span>, <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">)</span>,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">FK_Track_Albums_AlbumID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">FOREIGN</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">AlbumID</span><span style="color: rgba(255, 0, 0, 1)">]</span>) <span style="color: rgba(0, 0, 255, 1)">REFERENCES</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span>) <span style="color: rgba(0, 0, 255, 1)">ON</span> <span style="color: rgba(0, 0, 255, 1)">DELETE</span> <span style="color: rgba(0, 0, 255, 1)">CASCADE</span></span><span style="color: rgba(0, 0, 0, 1)">
);</span></pre>
</div>
<p>由于一个 Album 有多首曲子(Track),所以 Track 需要建立表,而 Track 只引用一个 Performer 实例,故,Track 的 Artist 属性的处理就跟复合属性很像了。Performer 类没有单独建表,而是把它的属性成员分散在 Track 表中。</p>
<p>注意 Track 表是有两个主键的:曲目自己的 ID 和专辑 ID,其中,AlbumID 是外键,引用专辑表中的记录。Track 表可能放着来自同一专辑的曲目,两个主键可以更好地标识。</p>
<p>看看两个表中的记录。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202508/367389-20250810182035539-333842767.png" alt="image" width="440" height="43" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202508/367389-20250810182420687-1162195842.png" alt="image" width="790" height="56" loading="lazy"></p>
<p> </p>
<p>但是,如果你坚持 Track 和 Performer 不要建表,那有没有办法解决呢?这个嘛,还真有,可以考虑只建 Album 表,把 Track 和 Performer 变成 JSON 列。其实就是将其化作 JSON,如同文本一样存到一个列中。</p>
<p>看配置代码:</p>
<div class="cnblogs_code">
<pre><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)"> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity</span><Album>(eb =><span style="color: rgba(0, 0, 0, 1)">
{
eb.OwnsMany(x </span>=> x.Tracks, ob =><span style="color: rgba(0, 0, 0, 1)">
{
<strong><span style="background-color: rgba(255, 255, 0, 1)">ob.ToJson();</span></strong>
ob.OwnsOne(d </span>=><span style="color: rgba(0, 0, 0, 1)"> d.Artist);
});
});
}</span></pre>
</div>
<p>这个 ToJson 方法只要在根对象上调用一次即可,不用每层对象都调用。比如,咱们这里是让 Tracks 属性存储一个 Track 集合(列表),所以先用 OwnsMany 确定 Album 实体与 Track 对象的主从关系,然后内层操作都用 OwnedNavigationBuilder 类来完成,这时,JSON 对象的顶层(根对象)就是这个 Track 集合,故在 ob 实例上调用一次 ToJson 方法就行,之后不管 Track 类嵌套了多少对象,都自动成为 JSON 对象的一部分(本例中, Performer 对象会自动被处理)。</p>
<p>再次运行程序,这时 Tracks 列只是普通的文本列。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(255, 0, 255, 1)">IDENTITY</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Title</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Year</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Style</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">TrackNum</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Tracks</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">PK_Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)">)
);</span></pre>
</div>
<p>然后查询一下这个表,看看 Tracks 字段里面存了啥东东。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">[
{
</span>"Comment": <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"Duration": 5556329<span style="color: rgba(0, 0, 0, 1)">,
</span>"ID": 0<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackIndex": 1<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackName": "\u6211\u662F\u521D\u97F3\u672A\u6765"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Artist"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"Age": 15<span style="color: rgba(0, 0, 0, 1)">,
</span>"Alias": "\u30EB\u30AA\u30FB\u30C6\u30F3\u30A4"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Birthday": "2012-07-12"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Brief": "\u6211\u4E0D\u5438\u718A\u732B\u70E7\u9999"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Gender": "\u52A8\u6001\u6027\u522B"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Name": "\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">,
</span>"StageName": "\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">
}
},
{
</span>"Comment": <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"Duration": 652224<span style="color: rgba(0, 0, 0, 1)">,
</span>"ID": 0<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackIndex": 2<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackName": "\u6211\u662F\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Artist"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"Age": 16<span style="color: rgba(0, 0, 0, 1)">,
</span>"Alias": "\u521D\u97F3\u30DF\u30AF"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Birthday": "2007-08-31"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Brief": "\u5C71\u4E1C\u7701\u6700\u5177\u4EE3\u8868\u6027\u519C\u4F5C\u7269"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Gender": "\u52A8\u6001\u6027\u522B"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Name": "\u521D\u97F3\u672A\u6765"<span style="color: rgba(0, 0, 0, 1)">,
</span>"StageName": "\u5929\u4E0B\u7B2C\u4E00\u7684fufu\u6BBF\u4E0B"<span style="color: rgba(0, 0, 0, 1)">
}
}
]</span></pre>
</div>
<p>现在因为 Track 类是存到 JSON 中,而不映射到数据表,所以 ID 属性不再作为主键,自然就不存在自增长了,所以,两条记录中,ID 都是 0。所以,在 Track 类定义时可以去掉 ID 属性。</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)"> Track
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TrackIndex {<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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> TrackName { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> Performer Artist { <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)">new</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)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span>? Duration { <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(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></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)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Comment {<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>再运行一遍应用程序,生成的 JSON 中就没有 ID 字段了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">[
{
</span>"Comment": <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"Duration": 5556329<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackIndex": 1<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackName": "\u6211\u662F\u521D\u97F3\u672A\u6765"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Artist"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"Age": 15<span style="color: rgba(0, 0, 0, 1)">,
</span>"Alias": "\u30EB\u30AA\u30FB\u30C6\u30F3\u30A4"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Birthday": "2012-07-12"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Brief": "\u6211\u4E0D\u5438\u718A\u732B\u70E7\u9999"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Gender": "\u52A8\u6001\u6027\u522B"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Name": "\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">,
</span>"StageName": "\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">
}
},
{
</span>"Comment": <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"Duration": 652224<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackIndex": 2<span style="color: rgba(0, 0, 0, 1)">,
</span>"TrackName": "\u6211\u662F\u6D1B\u5929\u4F9D"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Artist"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"Age": 16<span style="color: rgba(0, 0, 0, 1)">,
</span>"Alias": "\u521D\u97F3\u30DF\u30AF"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Birthday": "2007-08-31"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Brief": "\u5C71\u4E1C\u7701\u6700\u5177\u4EE3\u8868\u6027\u519C\u4F5C\u7269"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Gender": "\u52A8\u6001\u6027\u522B"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Name": "\u521D\u97F3\u672A\u6765"<span style="color: rgba(0, 0, 0, 1)">,
</span>"StageName": "\u5929\u4E0B\u7B2C\u4E00\u7684fufu\u6BBF\u4E0B"<span style="color: rgba(0, 0, 0, 1)">
}
}
]</span></pre>
</div>
<p>JSON 列的字段名默认与属性名(Tracks)相同,你如果想用其他名字,可以在调用 ToJson 方法时指定。</p>
<div class="cnblogs_code">
<pre>modelBuilder.Entity<Album>(eb =><span style="color: rgba(0, 0, 0, 1)">
{
eb.OwnsMany(x </span>=> x.Tracks, ob =><span style="color: rgba(0, 0, 0, 1)">
{
ob.<strong><span style="background-color: rgba(255, 255, 0, 1)">ToJson(</span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">track_list</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>;
ob.OwnsOne(d </span>=><span style="color: rgba(0, 0, 0, 1)"> d.Artist);
});
});</span></pre>
</div>
<p>在创建数据表的时候,列名就会自动变更。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(255, 0, 255, 1)">IDENTITY</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Title</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Year</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">Style</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">TrackNum</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">int</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">track_list</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">nvarchar</span>(<span style="color: rgba(255, 0, 255, 1)">max</span>) <span style="color: rgba(0, 0, 255, 1)">NULL</span></span></strong><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> <span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">PK_Albums</span><span style="color: rgba(255, 0, 0, 1)">]</span> <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span> (<span style="color: rgba(255, 0, 0, 1)">[</span><span style="color: rgba(255, 0, 0, 1)">ID</span><span style="color: rgba(255, 0, 0, 1)">]</span><span style="color: rgba(0, 0, 0, 1)">)
);</span></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/367389/202508/367389-20250810212727273-1562563444.png" alt="image" width="706" height="38" loading="lazy"></p>
<p> </p>
<p>===============================================================================================</p>
<p>好了,正题今天就水到这儿了,下面是老周讲故事时间。</p>
<p>曾在微博上收到一条私信说:老周,我感觉你很厉害,好像啥都研究得那么透彻。真佩服你!</p>
<p>这里要纠正一下,老周并不是啥都能研究得很透彻,比如人心,老周就无法研究。其实,老周只不过是这儿会一点,那儿会一点,啥都会,啥都不精。如此而已。</p>
<p>大伙伴们不用佩服老周的,只要你能确定:你的智商和脑子是正常的,那,你也能做到像老周这样的。老周从小就养成一个毛病,爱钻牛角尖。许多事情,仅仅知道是啥是不满足的,就很好奇想要弄清楚为什么。所以在学习一个东西,老周习惯性地把重点放在原理上,为什么会这样,为什么要这样做……老周一向比较自律,能静下心来做自己想做的事。也许,从小练书法确实是有好处的,人不会太急躁。尽管人的精力是有限的,不可能把世上的事情都弄明白。可是,能弄明白一件算一件,好歹对得起自己。如果自己花了心思去学习一个东西,连那是啥玩意儿都搞不懂,那岂不是浪费大好光阴吗?从小老师就教我们(不知道现在的老师还教不教),一寸光阴一寸金,下一句就不必说了吧。所以你看到老周写的文章,很喜欢告诉大伙原理性的东西,哪怕老周写的东西可能有错。至少这也是我的感悟,有错误的话你可以发个私信告诉我的。</p>
<p>现在有些人被别有用心的人误导,连放个P都追求快,快,快,赶着投胎似的。实际上,你可否想过,越是浮躁,你最终的损失越大。同样是花了时间和精力在某件事情上,你这搞搞那搞搞,最后连跟毛都搞不清楚,而别人是一件一件的梳理得一清二楚三通透。到最后,你觉得你比别人跑得更远了吗?</p>
<p>所以啊,有些事情是需要一个过程,该走的路就得走,再急也是徒增烦恼。小时候一直不明白一句话“烦恼即菩提”,这烦恼和菩提树是八竿子打不着的,毫无关系。长大后就懂了,就是毫无关系啊,烦恼就是你给自己找苦吃,把毫无关系的事情硬是往自己的情绪上塞。空想往往不会有收获,但抱着试一试的心态行动了,说不定就有所感悟了。</p>
</div><br><br>
来源:https://www.cnblogs.com/tcjiaan/p/19030612
頁:
[1]