原文地址: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. CRM客户关系管理系统(九)

    第九章.filter_horizontal优化和kingadmin删除功能 9.1.filter_horizontal优化 (1)添加Choose ALL 和Remove ALL table_obj_ ...

  2. table中的一些另类标签

    thead/tbody/tfoot/caption/colgroup 注意:thead/tbody/tfoot三部分内容不受编辑顺序影响只和标签本身含义从头到脚显示不能把两个tbody的内容进行合并单 ...

  3. Selenium2Lib库之输入常用关键字实战

    4.1 Input Text关键字 按F5 查看Input Text关键字的说明,如下图: Input Text 关键字用于向文本框输入内容,需要传2个参数(文本框的元素定位和输入的值). 项目例子: ...

  4. (Lesson2)根据类名称和属性获得元素-JavaScript面向对象

    描述:在编写选择器的时候遇到的一根问题,我需要实现Jquery的选择器功能,第一个根据ID获取Element非常简单,第二个根据类(class)去获取Element集合,这个相对复杂,而根据name和 ...

  5. java 保留字段volatile、transient、native、synchronized

    1.volatile Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程.当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享 ...

  6. 节流throttle和防抖debounce

    underscore.js提供了很多很有用的函数,今天想说说其中的两个.这两个函数都用于限制函数的执行. debounce 在解释这个函数前,我们先从一个例子看下这个函数的使用场景.假设我们网站有个搜 ...

  7. cmd登录远程Oracle数据库

    在cmd中输入  user/password@Ip:port/sid    例如:  laoda/123@192.168.4.161:1521/orcl      laoda是用户名,123是密码.

  8. mysql explain rows理解

    在MySQL性能调试中,常常使用EXPLAIN解释MySQL执行计划,从而用来估算性能耗时.其中,rows用来表示在SQL执行过程中会被扫描的行数,该数值越大,意味着需要扫描的行数,相应的耗时更长.但 ...

  9. 设置ActiveMQ的访问密码

    1.设置ActiveMQ的访问密码,以提高ActiveMQ的安全性 2.在ActiveMQ的conf目录的activemq.xml中添加账号密码 2.1  添加的代码如下 <!-- 添加访问Ac ...

  10. 最值得收藏的java技术博客(Java篇)

    第一个:java_my_life 作者介绍:找不到原作者信息.大概做了翻阅全部是2012年的博客. 博客主要内容:主要内容是关于Java设计模式的一些讲解和学习笔记,在相信对学习设计模式的同学帮助很大 ...