zoukankan      html  css  js  c++  java
  • 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));
     }
    
  • 相关阅读:
    JSON操作技巧
    我的前端学习历程(转)
    sql指南网址
    using 和try/catch区别和注意点
    【转】StringBuffer的用法与string的区别
    【转】比较page、request、session、application的使用范围
    【转】StringBuilder用法
    【转】.Net高级技术——IDisposable
    【转】.NET快速查找某个类所在的命名空间
    【转】VS2010安装包制作
  • 原文地址:https://www.cnblogs.com/HelloZyjS/p/12727827.html
Copyright © 2011-2022 走看看