EF CodeFirst 数据注解实战指南
场景一:基础字段映射控制
通过特性精确控制实体属性与数据库列的映射关系:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DemoApp
{
public class Article
{
[Key]
public int ArticleKey { get; set; }
[Required]
public string Headline { get; set; }
[MaxLength(100)]
public string AuthorAlias { get; set; }
[NotMapped]
public string ArticleSlug => $"{Headline[..3]}-{AuthorAlias[..2]}".ToLower();
}
}
对应的仓储上下文:
using System.Data.Entity;
namespace DemoApp
{
public class AppDbContext : DbContext
{
public DbSet<Article> Articles { get; set; }
}
}
数据操作示例:
using System;
using System.Linq;
namespace DemoApp
{
class Program
{
static void Main()
{
SeedData();
FetchData();
}
static void SeedData()
{
using var ctx = new AppDbContext();
ctx.Articles.Add(new Article
{
Headline = "深入理解EF Core",
AuthorAlias = "代码匠人"
});
ctx.SaveChanges();
}
static void FetchData()
{
using var ctx = new AppDbContext();
foreach (var item in ctx.Articles)
{
Console.WriteLine($"{item.ArticleKey}: {item.Headline} by {item.AuthorAlias}");
}
}
}
}
生成的表结构:
CREATE TABLE [dbo].[Articles] (
[ArticleKey] INT IDENTITY(1,1) NOT NULL,
[Headline] NVARCHAR(MAX) NOT NULL,
[AuthorAlias] NVARCHAR(100) NULL,
CONSTRAINT [PK_Articles] PRIMARY KEY CLUSTERED ([ArticleKey] ASC)
);
[Key] 标记主键,[Required] 映射为 NOT NULL,[MaxLength] 限制字符串长度,[NotMapped] 排除计算属性持久化。整型主键默认启用自增,字符串默认采用 NVARCHAR(MAX)。
场景二:复合键与复合外键配置
处理多字段联合主键及外键关联:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DemoApp
{
public class License
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int LicenseCode { get; set; }
[Key]
[Column(Order = 1)]
public string Authority { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidUntil { get; set; }
}
public class LicenseLog
{
[Key]
public int LogId { get; set; }
public DateTime OperationTime { get; set; }
public string OperationType { get; set; }
[ForeignKey("License")]
[Column(Order = 0)]
public int LicenseCode { get; set; }
[ForeignKey("License")]
[Column(Order = 1)]
public string Authority { get; set; }
public License License { get; set; }
}
}
生成的数据库结构:
CREATE TABLE [dbo].[Licenses] (
[LicenseCode] INT NOT NULL,
[Authority] NVARCHAR(128) NOT NULL,
[ValidFrom] DATETIME NOT NULL,
[ValidUntil] DATETIME NOT NULL,
CONSTRAINT [PK_Licenses] PRIMARY KEY CLUSTERED ([LicenseCode] ASC, [Authority] ASC)
);
CREATE TABLE [dbo].[LicenseLogs] (
[LogId] INT IDENTITY(1,1) NOT NULL,
[OperationTime] DATETIME NOT NULL,
[OperationType] NVARCHAR(MAX) NULL,
[LicenseCode] INT NOT NULL,
[Authority] NVARCHAR(128) NOT NULL,
CONSTRAINT [PK_LicenseLogs] PRIMARY KEY CLUSTERED ([LogId] ASC),
CONSTRAINT [FK_Logs_Licenses] FOREIGN KEY ([LicenseCode], [Authority])
REFERENCES [dbo].[Licenses] ([LicenseCode], [Authority])
);
CREATE NONCLUSTERED INDEX [IX_LicenseComposite]
ON [dbo].[LicenseLogs]([LicenseCode] ASC, [Authority] ASC);
Column(Order) 用于指定复合键中字段的排序优先级,外键字段顺序必须与引用主键保持一致。DatabaseGeneratedOption.None 禁用自增,由应用层赋值;其他选项包括 Identity(数据库生成)和 Computed(计算列)。
场景三:复杂类型的扁平化映射
将值对象嵌入实体表,避免单独建表:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DemoApp
{
public class Post
{
public int PostId { get; set; }
[Required]
public string Subject { get; set; }
public PostMetadata Meta { get; set; } = new PostMetadata();
}
[ComplexType]
public class PostMetadata
{
public DateTime? FirstPublished { get; set; }
[MaxLength(500)]
public string Summary { get; set; }
}
}
上下文配置(仅注册聚合根):
public class AppDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
}
数据操作:
using var ctx = new AppDbContext();
ctx.Posts.Add(new Post
{
Subject = "设计模式精讲",
Meta = new PostMetadata
{
FirstPublished = DateTime.UtcNow,
Summary = "本文深入剖析常用设计模式..."
}
});
ctx.SaveChanges();
生成的表结构:
CREATE TABLE [dbo].[Posts] (
[PostId] INT IDENTITY(1,1) NOT NULL,
[Subject] NVARCHAR(MAX) NOT NULL,
[Meta_FirstPublished] DATETIME NULL,
[Meta_Summary] NVARCHAR(500) NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY CLUSTERED ([PostId] ASC)
);
复杂类型无独立主键,其属性以 {ComplexType}_{Property} 命名模式展开到引用实体的表中。若类未显式标记 [ComplexType] 且无实体引用,EF 也会自动识别为复杂类型。
场景四:表名与列自定义映射
重写默认命名规则,适配遗留数据库或团队规范:
using System.ComponentModel.DataAnnotations.Schema;
namespace DemoApp
{
[Table("cms_Content", Schema = "content")]
public class Post
{
public int PostId { get; set; }
[Column("BodyText", TypeName = "ntext")]
public string Content { get; set; }
}
}
生成的表结构:
CREATE TABLE [content].[cms_Content] (
[PostId] INT IDENTITY(1,1) NOT NULL,
[BodyText] NTEXT NULL,
CONSTRAINT [PK_cms_Content] PRIMARY KEY CLUSTERED ([PostId] ASC)
);
[Table] 特性指定表名与架构,[Column] 特性可重命名列并显式声明数据类型(如 NTEXT、VARCHAR 等)。
场景五:并发冲突检测机制
实现乐观并发控制,防止更新丢失:
using System;
using System.ComponentModel.DataAnnotations;
namespace DemoApp
{
public class Document
{
public int DocId { get; set; }
[ConcurrencyCheck]
public string EditorName { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
}
生成的表结构:
CREATE TABLE [dbo].[Documents] (
[DocId] INT IDENTITY(1,1) NOT NULL,
[EditorName] NVARCHAR(MAX) NULL,
[RowVersion] ROWVERSION NOT NULL,
CONSTRAINT [PK_Documents] PRIMARY KEY CLUSTERED ([DocId] ASC)
);
[Timestamp] 映射为数据库的 ROWVERSION 类型,每次更新自动变更。[ConcurrencyCheck] 标记的字段在生成 UPDATE 语句时,会将原始值加入 WHERE 条件进行校验。若执行 SaveChanges 时检测到记录已被修改,将抛出 DbUpdateConcurrencyException。