原文地址: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." });
}
}
}

为了完成我们的业务,在StudentManagerAddStudent方法中,我们需要完成两步操作

  1. 添加学生信息
  2. 将学生分配给指定班
	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(); }
}

这里我们使用StudentRepositoryAddStudent方法来完成保存学生信息,使用GroupRepositoryAssignStudentToGroup方法来将学生分配给班级。

这里,其实不应该将保存学生信息和分配班级都放在这里,可以使用事件发布/订阅将其分配班级的逻辑移动到别处。

针对保存学生信息的操作,代码很简单。

	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中如何获取上下文中操作过的实体的更多相关文章

  1. EF Core中执行Sql语句查询操作之FromSql,ExecuteSqlCommand,SqlQuery

    一.目前EF Core的版本为V2.1 相比较EF Core v1.0 目前已经增加了不少功能. EF Core除了常用的增删改模型操作,Sql语句在不少项目中是不能避免的. 在EF Core中上下文 ...

  2. EF Core中如何正确地设置两张表之间的关联关系

    数据库 假设现在我们在SQL Server数据库中有下面两张表: Person表,代表的是一个人: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ...

  3. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  4. EF Core中避免贫血模型的三种行之有效的方法(翻译)

    Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...

  5. EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的

    我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...

  6. EF Core 通过延迟加载获取导航属性数据

    EF 6及以前的版本是默认支持延迟加载(Lazy Loading)的,早期的EF Core中并不支持,必须使用Include方法来支持导航属性的数据加载. 当然在EF Core 2.1及之后版本中已经 ...

  7. 文章翻译:ABP如何在EF core中添加数据过滤器

    原文地址:https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-cor ...

  8. EF Core中的多对多映射如何实现?

    EF 6.X中的多对多映射是直接使用HasMany-HasMany来做的.但是到了EF Core中,不再直接支持这种方式了,可以是可以使用,但是不推荐,具体使用可以参考<你必须掌握的Entity ...

  9. EF Core中DbContext可以被Dispose多次

    我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...

随机推荐

  1. java之Spring(AOP)-Annotation实现添加切面

    我们已经知道之前的切面添加方式(动态代理),是定义了一个实现了InvocationHandler接口的Handlerservice类,然后 在这个类内部写好切面逻辑,包括切面放置的位置,很显然下面的这 ...

  2. 如何优雅的关闭Java线程池

    面试中经常会问到,创建一个线程池需要哪些参数啊,线程池的工作原理啊,却很少会问到线程池如何安全关闭的. 也正是因为大家不是很关注这块,即便是工作三四年的人,也会有因为线程池关闭不合理,导致应用无法正常 ...

  3. CSS 静态进度条效果

    今天学习到了实现一个静态进度条的方法,固写一篇笔记稳固一下自己的知识. 最终的效果如下,进度条放在一个框里,水平宽自适应. 现在就开始,首先写一个进度条先. .progress-bar{ /* 进度条 ...

  4. ajax基本介绍

    AJAX即"Asynchronous Javascript And XML"(异步JavaScript和XML[Extensible Markup Language] ),是指一种 ...

  5. .net Core 微服务框架 surging 使用

    surging 是一个分布式微服务框架,提供高性能RPC远程服务调用,采用Zookeeper.Consul作为surging服务的注册中心, 集成了哈希,随机,轮询作为负载均衡的算法,RPC集成采用的 ...

  6. wcf双工通讯

    首先说一个服务引用不成功的解决办法: 需要把服务端配置文件中的Binding换成: <endpoint address="" binding="BasicHttpB ...

  7. Spring Boot实战笔记(一)-- Spring简介

    一.Spring 概述 Spring框架是一个轻量级的企业级开发的一站式解决方案.所谓的解决方案就是可以基于Spring解决所有的Java EE开发的所有问题. Spring框架主要提供了Ioc(In ...

  8. Python中的 socket示例

    linux send与recv函数详解   1 #include <sys/socket.h> 2 ssize_t recv(int sockfd, void *buff, size_t ...

  9. Spring Boot Favicon配置

    http://blog.csdn.net/xiaolyuh123/article/details/72403226

  10. SSM-SpringMVC-16:SpringMVC中小论注解式开发之访问方式篇

     ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 访问方式可以指定,打个比方,你通过get方式进入登陆页面,通过post发送ajax数据库校验或者post提交 ...