[小技巧]EF Core中如何获取上下文中操作过的实体
原文地址:https://www.cnblogs.com/lwqlun/p/10576443.html
作者:Lamond Lu
源代码:https://github.com/lamondlu/EFCoreFindSample

背景介绍
当我们在工作单元(UnitOfWork)中使用EF/EF Core的时候,为了要保持事务,一个用户操作只能调用一次SaveChange方法,但是有时候一个用户操作需要调用多个Repository,并且他们操作的实体是关联的。这时候在一个Repository中获取另外一个Repository中添加/修改/删除的实体就变成了一个问题。
问题说明
当前我们做一个学生管理系统,学生和班之间是多对多关系,一个学生可以属于多个班, 因此我们创建了如下的EF上下文。
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Group> Groups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentGroup>().HasKey(p => new { p.GroupId, p.StudentId });
base.OnModelCreating(modelBuilder);
}
}
[Table("Student")]
public class Student
{
public Student()
{
StudentGroups = new List<StudentGroup>();
}
[Key]
public Guid StudentId { get; set; }
public string Name { get; set; }
public int Credits { get; set; }
public virtual ICollection<StudentGroup> StudentGroups { get; set; }
}
[Table("Group")]
public class Group
{
[Key]
public Guid GroupId { get; set; }
public string GroupName { get; set; }
}
[Table("StudentGroup")]
public class StudentGroup
{
public Guid StudentId { get; set; }
public Guid GroupId { get; set; }
[ForeignKey("StudentId")]
public virtual Student Student { get; set; }
[ForeignKey("GroupId")]
public virtual Group Group { get; set; }
}
在用户界面上,我们允许用户在添加学生的时候,同时将学生分配到一个班级中。
因此我们的控制器代码如下:
public class StudentController : ControllerBase
{
private StudentManager _studentManager = null;
public StudentController(StudentManager studentManager)
{
_studentManager = studentManager;
}
// GET api/values
[HttpPost]
public IActionResult Post([FromBody]AddStudentDTO dto)
{
try
{
_studentManager.AddStudent(dto.Name, dto.GroupId);
return StatusCode(201);
}
catch
{
return StatusCode(500, new { message = "Unexpected Issue." });
}
}
}
为了完成我们的业务,在StudentManager的AddStudent方法中,我们需要完成两步操作
- 添加学生信息
- 将学生分配给指定班
public class StudentManager
{
private IUnitOfWork _unitOfWork;
public StudentManager(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void AddStudent(string studentName, Guid groupId)
{
var newStudentId = Guid.NewGuid();
_unitOfWork.StudentRepository.AddStudent(newStudentId, studentName);
_unitOfWork.GroupRepository.AssignStudentToGroup(newStudentId, groupId);
_unitOfWork.Commit();
}
}
这里我们使用StudentRepository的AddStudent方法来完成保存学生信息,使用GroupRepository的AssignStudentToGroup方法来将学生分配给班级。
这里,其实不应该将保存学生信息和分配班级都放在这里,可以使用事件发布/订阅将其分配班级的逻辑移动到别处。
针对保存学生信息的操作,代码很简单。
public class StudentRepository : IStudentRepository
{
private TestDbContext _dbContext;
public StudentRepository(TestDbContext dbContext)
{
_dbContext = dbContext;
}
public void AddStudent(Guid studentId, string name)
{
_dbContext.Students.Add(new Student
{
StudentId = studentId,
Name = name,
Credits = 0
});
}
}
但是当我们继续编写AssignStudentToGroup方法时就会遇到问题,我们该如何获取到前面方法中添加的Student实体?
这时候,有同学会去尝试
_dbContext.Students.Where(p=>p.StudentId = studentId)
你会发现它获取不到你想要的对象,原因是这条语句进行的是数据库查询,当前新增的Student对象还没有保存到数据库
那么如何解决这个问题呢?这里有2种解决方案
- 从
ChangeTracker上获取 - 使用
Find方法获取
从ChangeTracker上获取
ChangeTracker是EF/EF Core中的核心对象,在这个对象中记录了当前EF上下文,操作过的所有实体,实体状态及实体属性的变更。
ChangeTracker中的Entries泛型方法可以帮助我们获取到当前上下文中操作过的指定类型实体集合。
public void AssignStudentToGroup(Guid studentId, Guid groupId)
{
Student student = _dbContext.ChangeTracker.Entries<Student>().FirstOrDefault(p => p.Entity.StudentId == studentId).Entity;;
if (student == null)
{
throw new KeyNotFoundException("The student id could not be found.");
}
student.StudentGroups.Add(new StudentGroup
{
StudentId = studentId,
GroupId = groupId
});
}
但是这样写会出现一个问题,如果我想为一个数据库中已经存在的学生分配班级,调用这个方法就会出现问题,因为该实体还未加载到ChangeTracker中, 所以我们这里还需要使用_dbContext.Students.First方法进行数据库查询.
public void AssignStudentToGroup(Guid studentId, Guid groupId)
{
Student student;
if (_dbContext.ChangeTracker.Entries<Student>().Any(p => p.Entity.StudentId == studentId))
{
student = _dbContext.ChangeTracker.Entries<Student>().First(p => p.Entity.StudentId == studentId).Entity;
}
else if (_dbContext.Students.Any(p => p.StudentId == studentId))
{
student = _dbContext.Students.First(p => p.StudentId == studentId);
}
else
{
throw new KeyNotFoundException("The student id could not be found.");
}
student.StudentGroups.Add(new StudentGroup
{
StudentId = studentId,
GroupId = groupId
});
}
至此,整个方法的修改就完成了。如果你觉着这种方式比较繁琐,请继续看下面的Find方法。
使用Find方法
EF/EF Core中其实还提供了一个Find方法,以下是该方法的方法签名。
// Summary:
// Finds an entity with the given primary key values. If an entity with the given
// primary key values is being tracked by the context, then it is returned immediately
// without making a request to the database. Otherwise, a query is made to the database
// for an entity with the given primary key values and this entity, if found, is
// attached to the context and returned. If no entity is found, then null is returned.
//
// Parameters:
// keyValues:
// The values of the primary key for the entity to be found.
//
// Returns:
// The entity found, or null.
public virtual TEntity Find([CanBeNullAttribute] params object[] keyValues);
从这个Find方法的注释中,我们可以了解到,Find方法可以根据实体主键查询实体。但是它的优点是,它会优先去ChangeTracker中查找,如果查找不到才会生成查询语句,进行数据库查询。
由此,我们可以使用Find方法修改AssignStudentToGroup方法,看起来比之前的代码简化了不少
public void AssignStudentToGroup(Guid studentId, Guid groupId)
{
Student student = _dbContext.Students.Find(studentId);
if (student == null)
{
throw new KeyNotFoundException("The student id could not be found.");
}
student.StudentGroups.Add(new StudentGroup
{
StudentId = studentId,
GroupId = groupId
});
}
[小技巧]EF Core中如何获取上下文中操作过的实体的更多相关文章
- EF Core中执行Sql语句查询操作之FromSql,ExecuteSqlCommand,SqlQuery
一.目前EF Core的版本为V2.1 相比较EF Core v1.0 目前已经增加了不少功能. EF Core除了常用的增删改模型操作,Sql语句在不少项目中是不能避免的. 在EF Core中上下文 ...
- EF Core中如何正确地设置两张表之间的关联关系
数据库 假设现在我们在SQL Server数据库中有下面两张表: Person表,代表的是一个人: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ...
- 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获
项目开发中的一些注意事项以及技巧总结 1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...
- EF Core中避免贫血模型的三种行之有效的方法(翻译)
Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...
- EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的
我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...
- EF Core 通过延迟加载获取导航属性数据
EF 6及以前的版本是默认支持延迟加载(Lazy Loading)的,早期的EF Core中并不支持,必须使用Include方法来支持导航属性的数据加载. 当然在EF Core 2.1及之后版本中已经 ...
- 文章翻译:ABP如何在EF core中添加数据过滤器
原文地址:https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-cor ...
- EF Core中的多对多映射如何实现?
EF 6.X中的多对多映射是直接使用HasMany-HasMany来做的.但是到了EF Core中,不再直接支持这种方式了,可以是可以使用,但是不推荐,具体使用可以参考<你必须掌握的Entity ...
- EF Core中DbContext可以被Dispose多次
我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...
随机推荐
- codeforces——961B. Lecture Sleep
本文为博主原创文章,未经允许不得转载. 我在csdn也同步发布了此文,链接 https://blog.csdn.net/umbrellalalalala/article/details/7989196 ...
- 手把手教你全家桶之React(二)
前言 上一篇已经讲了一些react的基本配置,本遍接着讲热更新以及react+redux的配置与使用. 热更新 我们在实际开发时,都有用到热更新,在修改代码后,不用每次都重启服务,而是自动更新.并而不 ...
- linux环境安装svn并进行多个源码库区分管理
关于svn的文档有很多大部分已Windows为例子,因公司没有Windows服务器经过一天的曲折终于初步安装了解了svn.下面一些经验希望能帮助新手 本文采用的yum安装(简单快速没必要源码) 1.y ...
- Nginx日志自动按日期存储
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器,因它的稳定性.丰富的功能集.示例配置文件和 ...
- 如何在python脚本开发做code review
在软件项目开发中,我们经常提到一个词“code review”.code review中文翻译过来就是代码评审或复查,简而言之就是编码完成后由其他人通过阅读代码来检查代码的质量(可编译.可运行.可读. ...
- arcEngine开发之查看属性表
这篇文章给出实现属性表功能的具体步骤,之后再对这些步骤中的代码进行分析. 环境准备 拖动TOCControl.MapControl控件到Form窗体上,然后拖动ContextMenuStrip控件至T ...
- vue技术分享-你可能不知道的7个秘密
前言 本文是vue源码贡献值Chris Fritz在公共场合的一场分享,觉得分享里面有不少东西值得借鉴,虽然有些内容我在工作中也是这么做的,还是把大神的ppt在这里翻译一下,希望给朋友带来一些帮助. ...
- 非常适用的Sourceinsight插件,提高效率【强力推荐】
转自:http://www.cnblogs.com/heiyue/p/6225975.html 一直使用sourceinsight编辑C/C++代码,sourceinsight是一个非常好用的编辑工具 ...
- Myeclipse10.7.1 导出war包报错
myeclipse10.7.1 导出war问题解决办法myeclipse10破解后,导出war包时报"SECURITY ALERT: INTEGERITY CHECK ERROR" ...
- 【C#】对异步请求处理程序IHttpAsyncHandler的理解和分享一个易用性封装
在asp.net项目中,添加一个[一般处理程序]来处理请求是很自然的事,这样会得到一个实现自IHttpHandler的类,然后只需在ProcessRequest方法中写上处理逻辑就行了.但是这样的一个 ...