这一小节的内容主要是EFCore对关联数据的查询和修改。
EFCore 保存关联数据
首先看下保存关联数据,以Province和City举例,这是一个一对多的关系。
实例一:
public IActionResult Index()
{
var province=new Province()
{
Name = "辽宁",
Population = 4000000,
Cities = new List
{
new City(){AreaCode = "024",Name = "沈阳"},
new City(){AreaCode = "0411",Name = "大连"}
}
};
context.Provinces.Add(province);
context.SaveChanges();
return View();
}
实例二:
此时没有明确指定Cities的外键,DbContext自己会搞明白。
public IActionResult Index()
{
var province = context.Provinces.Single(x => x.Name == "辽宁");
province.Cities.Add(new City
{
AreaCode="0413",
Name = "鞍山"
});
context.SaveChanges();
return View();
}
但是在离线情况下,需要使用外键。下面这个例子中,City没有被追踪,所以需要指定外键。
public IActionResult Index()
{
var city = new City
{
ProvinceId = 11,
AreaCode = "0421",
Name = "鞍山"
};
context.Cities.Add(city);
context.SaveChanges();
return View();
}
查询关联数据
- Eager Loading 预加载 一次查询的时候都查询出来。
- Query Projections 查询映射 我定义出来我们想要的结果是什么样的 ,然后我们再进行查询。
- Explicit Loading 显示加载 内存里已经存在一些数据了,然后再想从内存里加载一些它的关联数据。
- Lazy loading 懒加载
预加载
在entity framework core中,如果两个实体涉及到外键连接,查询的时候默认是只查自身而不会去查询外键表的。如果想要让查询结果包含外键实体,则需要使用public IActionResult Index() { var province = context.Provinces .Include(x => x.Cities) .ToList(); return View(); }
include
方法来让查询结果包含外键实体。
注意下Include只能跟在Province(DbSet)后面,后面还可以继续跟着过滤where等。
EFCore还支持继续向下钻取,比如City下一级是CityCompany,CityCompany的下一级是Company。可以这样写:
EFCore还支持一同加载多个关联属性,例如Cities拥有三个关联属性,可以这么写:public IActionResult Index() { var province = context.Provinces .Include(x => x.Cities) .ThenInclude(x=>x.CityCompanies) .ThenInclude(x=>x.Company) .ToList(); return View(); }
public IActionResult Index() { var cities= context.Cities .Include(x => x.Province) .Include(x=>x.CityCompanies) .Include(x=>x.Mayor) .ToList(); return View(); }
查询出来的关联数据都是基于DBSet集合的,换句话说每个CityEntity具有三个集合。Inculde会获取所有关联数据,并不会过滤查询。
Query Projections 查询映射
查询映射相当于自定义查询结果,方法里我们使用一个匿名类型,匿名类定义我们需要的属性。
public IActionResult Index()
{
var provincesInfo = context.Provinces
.Select(x => new
{
x.Name,
x.Id
})
.ToList();
return View();
}
provincesInfo 是匿名类型,这个集合只能在这个方法里面用。如果想在外面方法使用的话,需要使用dynamic类型。
private List Query()
{
var provincesInfo = context.Provinces
.Select(x => new
{
x.Name,
x.Id
})
.ToList();
return provincesInfo;
}
public IActionResult Index()
{
var provincesInfo = Query();
foreach (var p in provincesInfo)
{
Console.WriteLine(p.Name);
}
return View();
}
EFCore同样可以增加过滤条件进行关联查询,像下面这么写:
public IActionResult Index()
{
var provincesInfo = context.Provinces
.Select(x => new
{
x.Name,
x.Id,
x.Cities.Count,
Cities=x.Cities.Where(y=>y.Name=="沈阳").ToList()
})
.ToList();
return View();
}
EFCore进行过滤查询时,还可以根据字表City去查询Province,查询出来的是Province。
public IActionResult Index()
{
var provincesInfo = context.Provinces
.Where(x => x.Cities.Any(y=>y.Name=="沈阳"))
.ToList();
return View();
}
EFCore修改关联数据
在线修改关联数据
EFCore修改关联数据时,可以像下面这么写:
public IActionResult Index()
{
var provincesInfo = context.Provinces
.Include(x=>x.Cities)
.First(x=>x.Cities.Any());
var city = provincesInfo.Cities[0];
city.Name += " Updated";
context.SaveChanges();
//context.Cities.Remove(city);
return View();
}
这个查询是关联查询出province的city属性,然后这些province必须带有city,最后再取第一个province.
修改这个province的第一个city的名字。还可以进行删除,删除第一个city。
离线修改关联数据
离线状态下修改关联数据有两种方法,下面进行一下比较。
public IActionResult Index()
{
var provincesInfo = context.Provinces
.Include(x=>x.Cities)
.First(x=>x.Cities.Any());
var city = provincesInfo.Cities[0];
city.Name += " Updated";
//Method 1
context2.Cities.Update(city);
//Method 2
context2.Entry(city).State = EntityState.Modified;
context2.SaveChanges();
return View();
}
第一种方法的执行过程中,会有五次update的SQL语句,因为此时更新的是这个city关联的所有数据,即它关联的province和province下面的四个city(包括了它自己)。
通过观察更新前后的DbContext下的状态,可以看到有五个状态的变化。
第二种方法是利用Entry方法,这个时候修改city,它会忽略cities所有的关联属性,只更新这个city对象本身。
这个时候只执行一次update操作,对应context对象中的changecount也是1.