

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
我们不讲空洞的理论,而是用“手把手教”的方式,把EFCore最核心的两个知识点——属性配置和DbContext用法——揉进实战场景里:比如用数据注解快速配“必填项”“默认值”,用Fluent API解决复杂的字段映射(比如主键自增、外键关联);再比如DbContext的生命周期怎么管(依赖注入该选哪个生命周期?什么时候该dispose?)、实战中最容易踩的坑(比如上下文共享导致的数据脏读、配置项覆盖的问题)……每一步都有可直接复制的代码例子,每个知识点都对应“新手常犯的错误”。
不管你是刚摸EFCore的小白,还是学了很久却“只会看不会写”的开发者,跟着这篇内容走,就能快速把“属性怎么配、DbContext怎么用”变成自己的技能——不用再对着文档猜,也不用怕写代码出错,真正实现“一看就会,会了能用”。
你有没有过这种情况?刚学ASP.NET Core EFCore,对着实体类加了一堆注解,结果数据库里的字段要么名字不对、要么类型不匹配;或者用DbContext的时候总报错,要么数据串了、要么并发时崩溃?别慌,我之前帮三个朋友调过这些问题,今天把最核心的「属性配置」和「DbContext用法」拆成最直白的实战步骤,你跟着做,保准不会踩坑。
属性配置:从入门到不踩坑的实战技巧
先来说属性配置——这是连接「实体类」和「数据库表」的桥梁,新手最容易在这里栽跟头。比如你有个User
实体,想让UserName
对应数据库的user_name
字段、想让Age
是必填项,该怎么操作?其实就两种方法:数据注解和Fluent API,我帮你把这俩的用法和坑都扒清楚。
先讲数据注解——这是最直观的方式,直接在实体类的属性上加标签。比如你想让UserName
必填且最长50个字符、对应数据库的user_name
字段,就这么写:
public class User
{
[Key] // 标记主键
public int Id { get; set; }
[Column("user_name")] // 对应数据库字段名(解决实体名和表字段名不一致的问题)
[Required] // 数据库中该字段必填
[MaxLength(50)] // 字段最大长度50
public string UserName { get; set; }
[Range(18, 100)] // 年龄范围限制在18-100岁
public int Age { get; set; }
}
是不是很简单?但我要提醒你两个必踩的坑:第一,加了注解一定要同步数据库迁移——之前有个同事加了[Column("user_name")]
,结果没运行Add-Migration
生成迁移脚本、也没执行Update-Database
同步到数据库,最后数据库字段名还是UserName
,他还以为注解没生效;第二,数据注解有局限性——比如想配置「复合主键」(比如OrderDetail
的OrderId
+ProductId
)、「多对多关系」,数据注解根本搞不定,这时候就得用Fluent API。
再讲Fluent API——这是更灵活的方式,所有配置都写在DbContext
的OnModelCreating
方法里。比如刚才的User
实体,用Fluent API配置是这样的:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 配置User实体的属性和关系
modelBuilder.Entity(entity =>
{
entity.HasKey(e => e.Id); // 标记主键(对应[Key]注解)
// 配置UserName属性:对应user_name字段、必填、最长50字符
entity.Property(e => e.UserName)
.HasColumnName("user_name")
.HasMaxLength(50)
.IsRequired();
// 配置Age属性:范围18-100岁
entity.Property(e => e.Age)
.HasRange(18, 100);
});
}
我为什么推荐新手优先学Fluent API?因为它能解决数据注解搞不定的复杂场景。比如做「多对多关系」——之前有个项目要做Student
(学生)和Course
(课程)的关联,用数据注解得手动建中间表StudentCourse
,还要在两个实体里加导航属性;但用Fluent API,一行代码就搞定了:
modelBuilder.Entity()
.HasMany(s => s.Courses) // 学生有多个课程
.WithMany(c => s.Students) // 课程属于多个学生
.UsingEntity(j => j.ToTable("StudentCourse")); // 中间表名StudentCourse
是不是省了好多事?我帮你 了数据注解和Fluent API的优缺点,放个表格更清楚:
配置方式 | 常用场景 | 优点 | 缺点 |
---|---|---|---|
数据注解 | 简单字段配置(必填、长度、字段名) | 直观,写在实体类里一目了然 | 无法配置复杂关系(复合主键、多对多) |
Fluent API | 复杂关系、全局配置 | 灵活,支持所有配置场景 | 配置分散在DbContext里,需要找位置 |
其实新手刚开始可以先学数据注解——毕竟直观好懂;等遇到复杂场景(比如多对多、复合主键),再转Fluent API。我自己就是这么过来的,刚开始觉得Fluent API麻烦,后来做了个「订单详情」的复合主键项目,才发现它有多香——用数据注解得写两行[Key]
,还容易错,Fluent API一行modelBuilder.Entity().HasKey(od => new { od.OrderId, od.ProductId });
就搞定了。
DbContext用法:新手最容易搞错的3个关键点
说完属性配置,再讲DbContext——这是EFCore的「核心大脑」,管着数据库连接、数据跟踪、查询执行,但新手最容易在这里犯低级错误,比如用错生命周期、共享上下文导致崩溃。我帮你把最容易踩的坑列出来,一个个解决。
ASP.NET Core里用依赖注入注册DbContext,默认是Scoped(每个HTTP请求一个实例),你知道为什么吗?因为DbContext不是线程安全的——如果多个线程同时用同一个DbContext,会导致数据混乱、并发冲突。之前有个朋友踩过这个坑:他把DbContext注册成Singleton
(单例,全局一个实例),结果用户A修改资料时,用户B的资料也被改了,查了半天才发现是DbContext单例导致的「数据串流」——因为单例实例被所有请求共享,A的修改覆盖了B的。
那为什么Scoped合适?因为每个HTTP请求对应一个Scoped实例,刚好覆盖请求的生命周期——请求进来时创建DbContext,请求结束时销毁,既保证了线程安全,又不会浪费资源。你记住:除非你非常清楚自己在做什么,否则别改DbContext的生命周期,就用默认的Scoped。
我见过最离谱的错误,是有人把DbContext存到静态类里,比如:
public static class DbContextFactory
{
// 静态变量:全局共享一个DbContext实例
public static MyDbContext Context = new MyDbContext();
}
然后在代码里到处用DbContextFactory.Context
查询、修改数据——结果并发的时候直接报错「DbContext已被释放」或者「线程冲突」。为什么?因为静态变量是全局共享的,多个请求同时用同一个DbContext,肯定会冲突啊!
正确的做法是什么?用依赖注入获取DbContext——在Controller或者Service里通过构造函数注入:
public class UserService
{
private readonly MyDbContext _context;
// 通过依赖注入获取DbContext实例(每个请求一个)
public UserService(MyDbContext context)
{
_context = context;
}
// 用_context操作数据库
public async Task GetUserById(int id)
{
return await _context.Users.FindAsync(id);
}
}
如果是控制台程序或者非Web项目(比如定时任务),就用using
语句创建实例——用完自动销毁,避免资源泄露:
// 控制台程序里的正确用法:using包裹,自动Dispose
using(var context = new MyDbContext())
{
var users = await context.Users.ToListAsync();
// 做数据操作
}
这样每个实例都是独立的,绝对不会冲突。
DbContext内部管理着数据库连接,如果你不用using
或者依赖注入,用完不释放,会导致数据库连接池被占满——比如你在控制台程序里循环创建DbContext却不销毁,过一会儿就会报「无法获取数据库连接」的错误。为什么?因为每个DbContext都会占用一个数据库连接,用完不释放,连接池里的连接被占满,新的请求就拿不到连接了。
ASP.NET Core里不用手动释放,因为依赖注入框架会帮你调用Dispose
方法;但如果你是自己创建的DbContext实例(比如控制台程序、WinForm程序),一定要用using
包裹——或者手动调用Dispose
,不然连接池会被撑爆。我之前做过一个定时任务程序,没加using
,结果跑了半小时就崩了,查日志发现是「连接池已满」,加了using
立刻就好了。
对了,微软官方文档里也明确说了:「DbContext应该被用作短期组件,每个操作或请求创建一个实例,并在使用后释放」(引用自微软EFCore官方文档)——你看,权威来源都这么说,肯定没错。
今天讲的这些技巧,都是我帮朋友调BUG 出来的「血泪经验」——你要是按我说的做,至少能避开80%的入门坑。比如属性配置用数据注解入门,复杂场景用Fluent API;DbContext用默认的Scoped,别共享实例,用完释放。你现在是不是觉得EFCore没那么难了?
要是你按这些方法试了,遇到问题可以回来留言——我帮你看看,毕竟我踩过的坑比你见过的还多~
数据注解和Fluent API有什么区别?该选哪个?
数据注解是直接在实体属性上加标签,比如[Required]、[Column],优点是直观好懂,适合简单配置(比如必填、字段名);但碰到复杂场景(比如复合主键、多对多关系)就不行了。Fluent API是写在DbContext的OnModelCreating方法里,通过代码配置,虽然要找配置位置,但灵活度高,能搞定所有场景(比如多对多关联、复合主键)。
新手可以先学数据注解入门,等遇到复杂需求(比如做订单详情的复合主键)再用Fluent API——我之前做项目时,用数据注解得写两行[Key]还容易错,Fluent API一行HasKey就搞定了,特别香。
DbContext为什么默认用Scoped生命周期?
因为DbContext不是线程安全的!如果用Singleton(全局一个实例),多个用户同时操作会串数据——比如用户A改资料,用户B的资料也被改了;用Transient(每次请求都新建)又太浪费资源,每个小操作都要创建销毁。
Scoped刚好对应每个HTTP请求的生命周期,请求进来时建实例,请求结束销毁,既保证线程安全,又不会浪费资源,所以默认选它最合理——除非你非常清楚自己在做什么,不然别乱改。
用静态类存DbContext为什么会报错?
静态变量是全局共享的,所有请求都用同一个DbContext实例,多线程并发时会冲突:比如两个用户同时修改数据,会报“DbContext已被释放”或者“线程冲突”;更离谱的是,用户A的修改会覆盖用户B的数据——我朋友之前就踩过这坑,把DbContext存静态类里,结果用户资料全乱了,查了半天才找到原因。
Fluent API怎么配置复合主键?
复合主键就是用两个字段一起当主键(比如订单详情表的OrderId+ProductId),用Fluent API的HasKey方法就行。比如你有个OrderDetail实体,主键是OrderId和ProductId,代码这么写:modelBuilder.Entity().HasKey(od => new { od.OrderId, od.ProductId });——直接把两个字段包成匿名类型传进去,特别简单。
DbContext用完需要手动Dispose吗?
分情况:如果是通过依赖注入拿到的DbContext(比如Controller里构造函数注入的),不用手动Dispose——ASP.NET Core框架会在请求结束时帮你销毁;但如果是自己new的DbContext(比如控制台程序里new MyDbContext()),一定要用using包裹,或者手动调用Dispose,不然数据库连接池会被占满,导致“无法获取连接”的错误。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com