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都会更新一遍,这是没有必要的,我们可以分析一下为什么会是这种追踪方式:

update方法执行之后
update方法执行之后

快速监视内容
快速监视内容

通过快速监视,在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));
 }
posted @ 2020-04-18 20:18  HelloZyjS  阅读(221)  评论(0编辑  收藏  举报