合理设计实体模型,贴近业务真实场景
在使用ORM(对象关系映射)时,很多人一开始就把数据库表直接一对一映射成类,结果后期业务一变,代码就得大改。其实更好的方式是先理清楚业务边界。比如做订单系统时,别急着把order、user、address全做成独立实体然后层层关联。可以先看实际业务中哪些数据经常一起用,比如“订单+收货人信息”总是一起展示,那不妨在Order实体里内嵌一个ShippingInfo属性,减少不必要的JOIN查询。
避免过度依赖自动化的懒加载
很多ORM框架默认开启懒加载,看似方便,但在高并发服务中很容易引发N+1查询问题。比如循环遍历100个用户查他们的部门信息,每次触发一次数据库请求,瞬间拖垮性能。应该在关键接口中主动使用预加载(Eager Loading),一次性拉取所需关联数据。像在查询用户列表时明确指定Include(d => d.Department),这样生成的SQL会带上LEFT JOIN,效率提升明显。
写查询时优先考虑性能,而非代码简洁
有时候为了图省事,用链式调用拼出复杂的Where条件,最后发现生成的SQL语句嵌套太多,执行计划很差。这时候不如换种方式——对于复杂报表类查询,可以直接写原生SQL配合ORM的SqlQuery或FromSqlRaw方法。例如:
var results = context.OrderReports
.FromSqlRaw("SELECT o.id, u.name, SUM(i.amount) as total \n FROM orders o \n JOIN users u ON o.user_id = u.id \n JOIN items i ON o.id = i.order_id \n WHERE o.created_at >= {0} \n GROUP BY o.id, u.name", startDate)
.ToList();
虽然少了点“面向对象”的感觉,但响应速度快了不止一倍,用户打开页面不再卡顿。
控制上下文生命周期,别让它跨线程或太久存活
DbContext这类上下文对象不是线程安全的,如果在异步任务中复用同一个实例,容易出现数据错乱。正确的做法是在每个请求范围内创建独立实例,用完即释放。ASP.NET Core里用AddScoped注册服务就能做到。另外别把上下文传给仓储层以外的其他模块,尤其是缓存、消息队列处理这种长时间运行的逻辑,否则内存泄漏风险很高。
善用变更追踪,但也要知道何时关闭它
ORM的强大之处在于能自动记录对象变化并生成UPDATE语句。但在批量导入场景下,这个功能反而成了负担。比如要插入一万条日志记录,如果每条都让上下文追踪,内存占用飙升,速度也越来越慢。这时可以设置context.ChangeTracker.AutoDetectChangesEnabled = false,或者使用非追踪查询结合Bulk Insert工具来处理。
统一数据访问入口,避免散弹式修改
项目做大了以后,不同人都往Repository里加自己的查询方法,时间一长,同一个业务逻辑出现在三个地方,改一处漏两处。建议建立清晰的仓储分层结构,核心操作封装在基类,特定查询放在具体实现类,并通过接口暴露给上层服务。同时加上注释说明每个方法适用场景,新人接手也不容易出错。
迁移管理要纳入版本控制流程
每次改表结构都靠手动执行SQL?迟早会出问题。ORM提供的Migration功能应该和Git流程绑定。每次模型变更后生成迁移文件,提交到代码库,再通过CI/CD脚本在测试、生产环境逐步执行。这样回滚也有据可依,不会出现线上数据库和代码不一致的情况。