原文地址: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. ng-change事件中如何获取$event和如何在子元素事件中阻止调用父级元素事件(阻止事件冒泡)

    闲聊: 今天小颖要实现一个当改变了select内容后弹出一个弹框,并且点击select父元素使得弹框消失,这就得用到阻止事件的冒泡了:$event.stopPropagation(),然而小颖发现,在 ...

  2. spring的简单入门

    spring是一个轻量级的JavaEE解决方案,是众多优秀设计模式的整合.spring的核心是:(工厂)容器 1.设计模式:解决一些特定问题的经典代码.共有23中设计模式(工厂,单例,代理,适配,装饰 ...

  3. ScalaPB(4): 通用跨系统protobuf数据,sbt设置

    我们知道,在集群环境节点之间进行交换的数据必须经过序列化/反序列化处理过程,而在这方面protobuf是一个比较高效.易用的模式.用户首先在.proto文件中用IDL来定义系统中各种需要进行交换的数据 ...

  4. jenkins中集成commander应用

    jenkins中集成commander应用 jenkins 集成测试 promotion 最近参加公司的集成测试平台的开发,在开发中遇到了不少问题,两个星期的迭代也即将完成,在这也用这篇博客记录下开发 ...

  5. 深入理解Java NIO

    初识NIO: 在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存 ...

  6. mysql workbench EER图,里面的实线以及虚线的关系

    ERWin里面线代表实体间的三种关系:决定关系(Identifying Relationship),非决定关系(None-Identifying Relationship),多对多(Many-To-M ...

  7. cmd命令行下登陆备份导入导出msql数据

    1.进入服务,找到mysql服务,在属性里找到mysql的安装路径 2.登陆  mysql -h 192.168.0.11 -P 3310 -u root -p 如果是访问的本机并且端口是默认的,那么 ...

  8. S3C6410板子移植 Android2.2

    一:Android简介 1.什么是Android: Android是一种基于linux的自由及开放源代码的操作系统,主要适用于移动设备,如智能手机和平板电脑,是由google公司和开放手机联盟领导和开 ...

  9. Python Redis 的安装

    安装 可以去pypi上找到redis的Python模块: http://pypi.python.org/pypi?%3Aaction=search&term=redis&submit= ...

  10. Bootstrap3 多个模态对话框无法显示的问题

    http://blog.csdn.net/oarsman/article/details/51387426