004 Entity Framework Core 2.x P5 关联数据
004 Entity Framework Core 2.x P5 关联数据
博客园文章Id:12727827
添加关系数据
public IActionResult Demo1()
{
var province = new Province
{
Name = "安徽",
Population = 60_000_000,
Cities = new List<City>
{
new City {AreaCode = "024", Name = "芜湖"}, //无需指定外键值
new City {AreaCode = "025", Name = "马鞍山"}, //无需指定外键值
}
};
_context.Province.Add(province);
_context.SaveChanges();
return View(nameof(Index));
}
在上述代码中,在新增Province表数据的同时,也会新增City表数据,在表模型关系上,一个Province
具有多个City
,当执行到_context.Province.Add(province);
时,EFCore 会追踪对象Province
,并将其状态设置为追加
,并同时追加Province
中的导航属性Cities
,并且也将其状态设置为追加
,当执行到_context.SaveChanges();
会根据模型关系,将数据存入到Province
表,以及City
表中,此处有一个细节,以这种方式添加数据时,在子表数据创建时不需要为其指定主表外键值,因为EFCore会自动推断,并装填到数据库中.
如果我们想在一个现有的Privince中追加一个新的City我们应该怎么做呢?
public IActionResult Demo2()
{
var province = this._context.Province.Single(x=>x.Name == "安徽"); //查询出已存在的Province
province.Cities.Add(new City
{
AreaCode = "026",Name = "铜陵"
});
this._context.SaveChanges();
return View(nameof(Index));
}
通过被追踪的Privince,然后通过Privince的City导航属性,添加一个新的City,那么此City也会被追踪,然后进行保存即可,值得注意的是,在被追踪的情况下,我们不需要手动指明新添加的City属性Privince外键的值,EFCore会自动装填,但是如果是离线状态下那就是是另外一回事了.
当数据是离线状态我们想要插入数据,如何做呢?
在离线状态我们就需要使用到外键了!
public IActionResult Demo3()
{
var city = new City
{
ProvinceId = 1, //当需要添加的数据是离线状态的,我们需要手动的指明外键
AreaCode = "027",
Name = "黄山"
};
this._context.City.Add(city);
this._context.SaveChanges();
return View(nameof(Index));
}
查询关联数据
- Eager Loading 预加载
- 在一次查询的时候,使用Include方法,把数据的关联数据都查询出来就是预加载.
- Query Projections 查询映射
- 定义出我们想要的结果,再进行查询.
- Explicit Loading 显式加载
- 在内存中已经存在一些数据,然后在想从数据中,查询关联数据,就是显式加载.
- Lazy Loading 懒加载
预加载 Eager Loading
什么是预加载?预加载就是在一次查询中,使用Include方法,把数据关联的数据都查询出来就是预加载.
public IActionResult Demo4()
{
var provinces = this._context.Province
.Include(x => x.Cities) //把关联的子表也查询出来
.ToList();
return View(nameof(Index));
}
通过查询结果,我们可以看到和Privince
相关的子表City
数据也被查询出来了.
此处需要注意的是Include方法只能放在DbSet属性的后面.
如果我们想查询当前主表的数据,并且带上和这个主表有关的子表的数据,并且这个子表还有子表的数据...,我们都想查询出来,我们应该怎么做呢?我们可以使用ThenInclude
方法,但是这种查询方式,但是效率可能不太好.
var provinces = this._context.Province
.Include(x => x.Cities) //查询当前省份下的所有城市
.ThenInclude(x => x.CityCompanies) //多对多中间表
.ThenInclude(x => x.Company) //查询当前城市下的所有公司
.ToList();
如果一个表有多个外键,我们在查询这个表的时候,我们期望将这些关联表的数据都查询出来我们应该怎么做呢?
通过Include
方式来查询,即可.
public IActionResult Demo6()
{
var cities = this._context.City
.Include(x => x.Province) //通过Include来关联子表
.Include(x => x.CityCompanies) //通过Include来关联子表
.Include(x => x.Mayor) //通过Include来关联子表
.ToList();
return View(nameof(Index));
}
Include注意事项
总会把所有的关联数据都带上,它并不会过滤关联的数据,那么如何过滤关联数据呢?
查询映射 Query Projections
什么是查询映射? 定义出我们想要的结果,再进行查询,即为查询映射.
public IActionResult Demo7()
{
var provinceInfo = this._context.Province
.Select(x => new //在 select 方法中,通过匿名方法组织返回结果,就是查询映射
{
x.Name,
x.ProvinceId
})
.ToList();
return View(nameof(Index));
}
查询映射就是自定义查询结果的数据格式.
这里需要注意的是由于使用匿名类作为返回结果,所以如果我们想要在别的方法中使用这个返回结果,那么我们就需要使用到dynamic
类型来作为返回结果类型了,这样在调用方法处,只能使用.属性
的方式来调用,但是不怎么推荐这种用法因为性能会降低,如果有需求需要再别的方法中调用这个返回结构,我们可以封装一个实体类型来装载查询结果.示例代码如下:
public IActionResult Demo8()
{
var provinceInfo = this.Query();
foreach (var p in provinceInfo)
{
//注意此处的Name是通过.的方式获得的,是属于推断数据,
//所以我们必须事先知晓,其中是存在Name属性的
var temp = p.Name;
}
return View(nameof(Index));
}
public List<dynamic> Query()
{
var provinceInfo = this._context.Province
.Select(x => new
{
x.Name,
x.ProvinceId
})
.ToList<dynamic>();
return provinceInfo;
}
在查询映射的语法中,我们也可以将关联属性的结果也查询出来,并且也可以给这个关联属性,加上过滤条件,示例代码如下:
上面提到了Include
方式总会把所有的关联数据都带上,它并不会过滤关联的数据,那么如何过滤关联数据呢,下面给出解决方法.
public IActionResult Demo9()
{
var provincesInfo = this._context.Province
.Select(x => new
{
x.Name,
x.ProvinceId,
Cities = x.Cities.Where(y=>y.Name == "芜湖").ToList()
}).ToList();
return View(nameof(Index));
}
查询结果如下:
但是有的时候判断条件在关联属性上(即子表上),但是我们又不期望将关联属性的查询结果也带出来,那么我们需要这么做,示例代码如下:
public IActionResult Demo10()
{
var provincesInfo = this._context.Province
.Where(x => x.Cities.Any(y => y.Name == "芜湖"))
.ToList();
return View(nameof(Index));
}
查询结果:
修改关联数据
public IActionResult Demo11()
{
var provincesInfo = this._context.Province
.Include(x => x.Cities)
.First(x => x.Cities.Any());
var city = provincesInfo.Cities[0];
city.Name += "Updated";
this._context.SaveChanges();
return View(nameof(Index));
}
上述代码的含义是,我们希望查询省份,并且要求这个省份不能没有城市,根据查询结果,获取该省份下的第一个城市,将这个城市名称进行修改后,提交保存. (数据被追踪的状态下实现)
在数据被追踪的情况下,修改数据还是比较简单的,那么在离线状态下我们应该怎么做到类似操作呢?代码如下:
public IActionResult Demo12()
{
var provincesInfo = this._context.Province
.Include(x => x.Cities)
.First(x => x.Cities.Any());
var city = provincesInfo.Cities[0];
city.Name += "Updated";
this._context2.Update(provincesInfo); //这条数据与_context2毫无关系,所以是对_context2来说,不是追踪数据.
this._context2.SaveChanges();
return View(nameof(Index));
}
但是上述写法不太好,EFCore上下文会将和city有关的Privince,以及Privince下的符合条件的所有City都会更新一遍,这是没有必要的,我们可以分析一下为什么会是这种追踪方式:
通过快速监视,在EFCore执行到_context.Cities.Update(city); 的时候上下文将和此City有关的所有数据都设置为了被追踪的状态,那么在_context2.SaveChanges()
的时候,就会将这些数据都会更新一遍,这并不是我们想要的结果,所以我们应该怎么实现自己想要的结果的方式呢?
我们只需要将我们想要的修改的对象,的状态设为Modified
就可以了,这样的话,在saveChanges的时候只会修改本身,示例代码如下:
public IActionResult Demo13()
{
var provincesInfo = this._context.Province
.Include(x => x.Cities)
.First(x => x.Cities.Any());
var city = provincesInfo.Cities[0];
city.Name += "Updated";
this._context2.Entry<City>(city).State = EntityState.Modified; //只修改city的追踪状态
this._context2.SaveChanges();
return View(nameof(Index));
}
删除关联数据
既然我们可以对关联的数据进行修改,那么我们也可以对关联属性进行删除,代码如下:
public IActionResult Demo14()
{
var provincesInfo = this._context.Province
.Include(x => x.Cities)
.First(x => x.Cities.Any());
var city = provincesInfo.Cities[0];
this._context.City.Remove(city); //移除被追踪的数据
this._context.SaveChanges();
return View(nameof(Index));
}
在数据被追踪的情况下,删除数据还是比较简单的,那么在离线状态下我们应该怎么做到类似操作呢?代码如下:
public IActionResult Demo15()
{
var provincesInfo = this._context.Province
.Include(x => x.Cities)
.First(x => x.Cities.Any());
var city = provincesInfo.Cities[0];
this._context2.Entry<City>(city).State = EntityState.Deleted; //编辑对象状态为待删除
this._context2.SaveChanges();
return View(nameof(Index));
}
- 上述文档参考自
关联数据