.NET兼职社区

一、先安装对应的数据库映射工具。一般对于的数据库映射工具包含EntityFrameworkCore的依赖。

1.一般EntityFrameworkCore(ORM映射工具)安装在你的数据项目工程中,这里我通过nuget安装的sqlserver的包。对应的数据和安装对应的包就行。包名如下:
Microsoft.EntityFrameworkCore.SqlServer

2.然后我们建立好对应的数据Model

3.Model建立完成后,我们建立数据库上下文文件如下:
在这里插入图片描述
该类需要继承DbContext,跟数据库的交互,都在这个类中进行。
其中DbSet的作用就是暴露Modle让他们能够在该类中正常使用。DbSet中的泛型相当于数据库中的表。
OnConfiguring可以做一些相关的配置,比如链接字符串。

二、安装进行数据迁移的命令工具。

1.Model建立好之后,我们可以安装对应的工具,进行数据迁移。安装如下包:
Microsoft.EntityFrameworkCore.Tools
同样该包也是安装在你的数据工程当中。

2.打开包管理控制台
在这里插入图片描述

3.进行数据迁移
先把默认项目选为你安装了包的项目,如图:
在这里插入图片描述
然后执行: get-help entityframework 获取所有的指令,一般用添加指令和更新指令就够了。

然后我们进行第一次数据迁移,输入add-migration Initial的指令,其中inital为迁移的名称,可以随便取。
在这里插入图片描述
会有如同的提示,大概意思就是你的启动项没有安装 Microsoft.EntityFrameworkCore.Design这个包,然后你去把这个包安装到你的启动项中,然后再执行一遍刚才的命令。

4.分析生成的文件。
当我们执行数据迁移成功后,会生成如图文件:
在这里插入图片描述
其中DemoContextModelSnapshot.cs它相当于是个快照,它非常重要,而且我们不能手动去修改,作用就是EFcore通过他去追踪我们Model的一个状态,比如说:我们又加了一个model,EFcore就会读取这个快照,看看他当前所追踪的快照是什么样,然后跟他以现在大model进行比较,比较出差异之后,就知道改一些什么东西了。
然后我们看下Initial这个类:
在这里插入图片描述
这个类继承了Migration这个类,相当于使用了他的API,这Initial类中就2个方法。
其中UP方法的作用是:对数据库做出的修改(里面的具体内容比较简单,自己可以看看。)。
Down方法:当修改有些问题的时候,我们要进行一个回滚,他就执行这个Down方法。

接着我们来执行生成数据库或者生成脚本

执行下面命名来生成sql脚本(一般生产环境下才会使用)
Script-Migration
开发环境一般使用一下命令进行数据库更新
update-database -verbose
其中verbose是可选参数,加了之后可以看到数据库生成的详细信息。一般不加。

执行完成后我们可以看到如图数据库中生成了4张对应的表,
在这里插入图片描述

其中三张是我自己创建的Model,另外__EFMigrationsHistory这个表是用来做数据迁移的历史纪录的。

另外上面我们采用的方式是code first方式,还有其他方式,详情请看微软文档。EF core官网
同样如果有写好的数据库脚本,也可以通过数据库脚本去生成对应的模型。详情请看微软文档。

三、接着我们来对模型中的字段做一些相应的限制。

首先打开nuget安装system.componentModel.Annotations相应的库,如图:
在这里插入图片描述
然后我们进行相关的引用:using System.ComponentModel.DataAnnotations;
然后我们就可以在相应的字段中进行注释。下图设置了一些常用注释,详细注释请参考微软官网:
在这里插入图片描述在这里插入图片描述
因为做了相应的更改,所以我们可以进行一次数据迁移。执行如下命令,进行更新操作。
Add-migration ChangeSomeOrioerties
如图:
在这里插入图片描述
执行成功后,我们可以看下执行后生成的对应文件。

using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace Demo.Data.Migrations
{
    public partial class ChangeSomeOrioerties : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "Leagues",
                type: "nvarchar(100)",
                maxLength: 100,
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(max)",
                oldNullable: true);

            migrationBuilder.AlterColumn<string>(
                name: "Country",
                table: "Leagues",
                type: "nvarchar(50)",
                maxLength: 50,
                nullable: false,
                defaultValue: "",
                oldClrType: typeof(string),
                oldType: "nvarchar(max)",
                oldNullable: true);

            migrationBuilder.AlterColumn<DateTime>(
                name: "DateOfEstablishment",
                table: "Clubs",
                type: "date",
                nullable: false,
                oldClrType: typeof(DateTime),
                oldType: "datetime2");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "Leagues",
                type: "nvarchar(max)",
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(100)",
                oldMaxLength: 100,
                oldNullable: true);

            migrationBuilder.AlterColumn<string>(
                name: "Country",
                table: "Leagues",
                type: "nvarchar(max)",
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(50)",
                oldMaxLength: 50);

            migrationBuilder.AlterColumn<DateTime>(
                name: "DateOfEstablishment",
                table: "Clubs",
                type: "datetime2",
                nullable: false,
                oldClrType: typeof(DateTime),
                oldType: "date");
        }
    }
}

这个文件是如何生成出来的呢,EF Core会先和之前的DemoContextModelSnapshot.cs也就是快照这个类进行对比,对比后EFcore就可以算出来你又做了哪些动作,然后达到状态同步的一个效果。
接着我们来分析一下上面贴出来的类,都进行了哪些动作。
我们可以看到上图贴的图片中相应的字段都进行了修改,
比如:UP函数中的DateOfEstablishment字段之前老的类型是

oldClrType: typeof(DateTime),
                oldType: "datetime2");

新类型为:

type: "date",

Down的操作就是如果有失误就会将刚刚的动作进行回滚。
另外相应的快照也会更新为最新的快照,保持一致。

然后我们执行下面命令对数据库进行更新操作。
update-database

然后我们可以看下迁移表
在这里插入图片描述
加了一条刚刚操作的数据,表示更新成功了。其他的字段属性都是差不多的,具体可以看下官方文档,然后进行对应的更新操作。

四、接着我们来对模型进行一对多,多对多,一对一的关系建立。

如图有如下关系:
在这里插入图片描述
如下是对应的三个模型:

1.先建立一对多的关系。

联赛模型

    public class League
    {
        public int Id { get; set; }
        [MaxLength(100)]  //最大长度为100
        public string Name { get; set; }
        [Required,MaxLength(50)] //Required为必填
        public string Country { get; set; }
    }

足球队模型

 public  class Club
    {

        public Club() 
        {
            Players = new List<Player>();
        }

        public int Id { get; set; }

        public string Name { get; set; }

        public string City { get; set; }
        [Column(TypeName="date")]//声明后数据库中该字段对应的类型就为date
        public DateTime DateOfEstablishment { get; set; }

        public string History { get; set; }

        public League League { get; set; }

        public List<Player> Players { get; set; }
    }

球员模型

 public  class Player
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public DateTime DateOfBirth { get; set; }

    }

上面的模型中我们可以看到,联赛、球员模型中都没有体现出一对多的关系,但是足球队模型中体现出来了,足球队中有个public League League { get; set; } 表明一个足球队必须对应一个联赛,反过来一个联赛中也可以有多个足球队。如果你不指定外键关联的话,EFcore会自动通过模型关系生成对应的外键关联。
如图:
在这里插入图片描述
Clubs表中已经对应生成了外键关联。
另外
public List Players { get; set; }
对应的这个导航属性的类型是一个集合。相当于另外一个方向的,此时Clubs是主表,Players 是子表,相当于一个足球队中可以有多个队员。我们在看下Players 表的设计,看有没有对应的外键。

在这里插入图片描述
可以看到EFcore也对应的自动的建立了外键关系。一对多的关系比较简单,就不多说了。

2.建立多对多的关系。

在这里插入图片描述

现在在上面的模型基础上,我们在建立一个比赛(Game)模型。一个队员可以对应多场比赛,一场比赛可以由多个队员参加,这种多对多的关系,EF Core是实现不了的,此时我们可以通过加入一个中间表GamePlayer来间接的去实现多对多的关系,一个队员可以参加多场比赛也就是对应多个GamePlayer而一场比赛又可以由多个队员参加相当于对应多个GamePlayer,此时就间接的实现了多对多的关系。

接着我们看下具体实现:
比赛模型

 public class Game
    {
        public Game() 
        {
            GamePlayers = new List<GamePlayer>();

        }


        // 约定 取名叫ID的都是主键。或者加【key】
        public int Id { get; set; }
        [Display(Name ="场数")]
        public int Round { get; set; }
        [Display(Name = "开赛时间")]  //加问好表示对应的数据库中的字段是可空的 DateTimeOffset是值类型
        public DateTimeOffset? StarTime { get; set; }

        public List<GamePlayer> GamePlayers { get; set; }

    }

中间表GamePlayer

  public class GamePlayer
    {
        //GamePlayer的主键在DBContext文件中OnModelCreating方法中设置
        public int PlayerId { get; set; }

        public int GameId { get; set; }



        //在GamePlayer中体现一对多的关系   一场比赛有多个队员  一个队员参加多场比赛
        public Game Game { get; set; }

        public Player Player { get; set; }
    }

更新了Player 模型
Player

  public  class Player
    {

        public Player() 
        {
            //初始化,防止出现空引用异常
            GamePlayers = new List<GamePlayer>();
        }
        public int Id { get; set; }

        public string Name { get; set; }

        public DateTime DateOfBirth { get; set; }

        //=====导航属性
        public List<GamePlayer> GamePlayers { get; set; }

    }

接着我们给GamePlayer添加联合主键,在DBContext文件中OnModelCreating方法中设置,代码如下:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //设置联合主键GamePlayer的主键=PlayerId+GameId
            //如果主键由多个属性组成,则指定一个匿名类型,包括属性 (post = > new {post.Title,post.BlogId}) 。
            modelBuilder.Entity<GamePlayer>().HasKey(x=>new { x.PlayerId,x.GameId});
        }

接着我们进行数据迁移的指令:
add-migration addGame

然后生成如下迁移文件

using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace Demo.Data.Migrations
{
    public partial class addGame : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Game",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Round = table.Column<int>(type: "int", nullable: false),
                    StarTime = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Game", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "GamePlayer",
                columns: table => new
                {
                    PlayerId = table.Column<int>(type: "int", nullable: false),
                    GameId = table.Column<int>(type: "int", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_GamePlayer", x => new { x.PlayerId, x.GameId });
                    table.ForeignKey(
                        name: "FK_GamePlayer_Game_GameId",
                        column: x => x.GameId,
                        principalTable: "Game",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_GamePlayer_Players_PlayerId",
                        column: x => x.PlayerId,
                        principalTable: "Players",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_GamePlayer_GameId",
                table: "GamePlayer",
                column: "GameId");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "GamePlayer");

            migrationBuilder.DropTable(
                name: "Game");
        }
    }
}

大概意思就是创建了2张表,建立了2个外键。

然后执行更新的命令:
update-database

到这来,我们多对多的关系就建立完毕了!

3.接着我们来建立一对一的关系。

在这里插入图片描述
假设每个球员都有一份自己的简历。

那么我们开始建立如下模型:

简历模型

 public class Resume
    {
        public int Id { get; set; }

        public string Description { get; set; }
        //相当于球员表的外建
        public int PlayerId { get; set; }
        //导航属性
        public Player Player { get; set; }

    }

队员信息Player做了如下更改

   //与简历表简历外键关系
        public int ResumeId { get; set; }
        //导航属性
        public Resume Resume { get; set; }

现在这2个类之间就是一对一的关系,这时候EF Core会选择这2个类之间的一个作为主体,但是有时候他会选错,所以最好我们自己指定一下。

进入DBcontex类,在OnModelCreating方法中自己指定一下主体关系。
添加如下代码:

 modelBuilder.Entity<Resume>()
                .HasOne(x => x.Player) //这句话意思就是一份简历对应一个球员
                .WithOne(x => x.Resume)//一个球员又带着一份简历
                .HasForeignKey<Resume>(x => x.PlayerId);//这句话意思就是一份简历有一个外键关联着PlayerId

这里Entity里面依赖的实体不管是Resume还是Player都可以,如果是Player把对应的关系换一下就可以。
这样一对一的关系就建立完成了。

接着可以进行数据迁移,迁移指令和上面一样,先进Add再进行Update。

更新完成后,我们可以看下数据库中的情况。
在这里插入图片描述
生成的sql语句

 CONSTRAINT [FK_Resume_Players_PlayerId] FOREIGN KEY ([PlayerId]) REFERENCES [dbo].[Players] ([Id]) ON DELETE CASCADE

可以看到已经建立了主外键关系。

五、接着使用EF core做一下增删改查。

5.1单条数据的添加

首先,如果我们需要和数据库进行交互的话,我们需要用到数据库上下文类,也就是DBContext。
添加操作代码如下:

  static void Main(string[] args)
        {
          // 1. 实例化数据库上下文类 
            using var context = new DemoContext(); 
          // 2. 创建要添加的数据 
            var serieA = new League
            {
                Country = "Italy",
                Name = "Serie A"
            };
           // 3. 进行添加操作。 这步操作只是把数据相应的添加到了上下文,此时并没有添加进入数据库。
            context.Leagues.Add(serieA);
            // 4. 进行保存   做了这步操作,才真正的把数据添加进入数据库。 你做了什么操作,他会对应的返回修改的条数给你。
            var count = context.SaveChanges();
            Console.WriteLine(count);
            Console.ReadKey();


        }

以上就是添加一条数据的操作,步骤已经通过注释的方式写出来了。

如果你想看这些代码具体做了什么操作,可以添加日志进行查看。

5.1.1查看添加操作的日志

首先我们需要安装对应的包。我们在nuget中给数据库操作项目添加如下包,Microsoft.Extensions.Logging.Console
然后在数据库上下文类中添加如下代码:

 public static readonly ILoggerFactory ConsikeLoggerFactory =
            LoggerFactory.Create(builder =>
            {
                //进行过滤
                builder.AddFilter((category, level) =>
                category == DbLoggerCategory.Database.Command.Name  //只要数据库执行命令
                && level == LogLevel.Information) //级别只记录LogLevel
                    .AddConsole();//输出到控制台

            });

然后我们需要在OnConfiguring这个方法中加入如下代码。
在这里插入图片描述
切记,日志输出需要写在数据库连接字符串之前。
这样我们就可以进行数据库动作的日志查看了。运行一下代码,控制台输入如图:
在这里插入图片描述
我们通过日志可以看到,添加的代码做了如下操作。就是sql语句中的inset into操作。

5.2多条数据的添加

5.2.1如果是同数据类型

我们可以使用如下2种方式添加,代码如下:

var serieB = new League
            {
                Country = "Italy",
                Name = "Serie B"
            };

            var serieC = new League
            {
                Country = "Italy",
                Name = "Serie C"
            };
            
  			//context.Leagues.AddRange(serieB,serieC); //直接添加
            context.Leagues.AddRange(new List<League> { serieB,serieC} ) ;//采用集合的方式添加
            var count = context.SaveChanges();

AddRange有重载方法,直接添加和采用集合方式添加是一样的。

5.2.2如果是不同类型的数据

代码如下:

  //查询指定的联赛
            var serieA = context.Leagues.Single(x => x.Name == "serie A");

            var serieB = new League
            {
                Country = "Italy",
                Name = "Serie B"
            };

            var serieC = new League
            {
                Country = "Italy",
                Name = "Serie C"
            };

            var milan = new Club
            {
                Name = "AC Milan",
                City = "Milan",
                DateOfEstablishment = new DateTime(1899, 12, 16),
                League = serieA   //把当前球队指定到特定的联赛   这样写就可以进行主外键关联
            };
            //==================不同类型数据添加方式=====================
            context.AddRange(serieB, serieC, milan);
            var count = context.SaveChanges();

如果是不同类型数据,我们直接通过context进行添加就行。EF能够自动识别给他添加到那个类型里面。
我们可以看下操作后的数据库结果:
在这里插入图片描述
数据都已经成功加入,并且主外键关系一一对应。

5.3查询操作

5.3.1精确查询

ef的查询一般使用Linq操作 ,linq一般有2种方式,一种是直接使用Linq方法进行查询,另一种是写linq语句
下面代码分别写了2种查询方式:

            // 方式1 Linq方法进行查询
            var leagues = context.Leagues.Where(x => x.Country == "Italy").ToList();

            // 方式2  Linq语句的方式进行查询
            var leagues2 = (from lg in context.Leagues
                            where lg.Country == "Italy" 
                            select lg).ToList();

方式1和方式2 执行的效果是一样的,一般都用方式1 这里需要注意的是。
当你Where之后返回的是IQueryable类型 此时只生成了对应的sql语句,而并没有执行,它会延迟加载 只有你ToList之后才会立即执行。
详细解释请看IQueryable类型的执行逻辑(延迟执行),认真看,你会有收获的。
我们看下执行的结果:
在这里插入图片描述
此时我们可以看到日志中生成的sql语句。查询条件是写死的。
现在我们用参数的形式进行查询。
在这里插入图片描述在这里插入图片描述
我们可以看到,sql语句也变成参数化了,但是 你并看不到参数的内容,他用?代替了。如果需要看到参数内容,我们加入一下代码:
在这里插入图片描述
然后在运行看,就可以看到参数结果呢。
在这里插入图片描述

5.3.2模糊查询

方式1
通过Contains函数实现,实现这样效果 country like “%a%” 代码如下:

var leagues = context.Leagues.Where(x => x.Country.Contains("a")).ToList();

方式2

   var leagues1 = context.Leagues.Where(x => 
            EF.Functions.Like(x.Country,"%a%")
            ).ToList();

方式1和方式2 实现的效果是一样的。
这里举例一下和ToList有等价效果的函数,这些函数都会使sql立即执行。

Tolist() 返回查询结果。
First() 返回第一条数据,没有数据会报错。
FirstOrDefault() 返回第一条数据 它可以有返回结果,也可以没有
Single() 符合查询条件的只能是一条数据
SingleOrDefault() 符合查询条件的只能是一条数据,或者没有数据
Last() 返回最后一条
LastOrDefault() 返回最后一条,可以为空
Count() 返回查询条数
LongCount() 返回表示序列中满足条件的元素的数量的
Min() 最小值
Max() 最大值
Sum() 计算总和
Average() 平均值
Find() 查找匹配的结果,这个不属于Linq方法,是Contex的方法,但也会立即执行

以上方法都有对应的异步方法。

有的方法有相应的where重载,比如我们返回第一条数据可以直接这样写:

 var leagues1 = context.Leagues.FirstOrDefault(x =>
           EF.Functions.Like(x.Name, "%B%")
           );
            Console.WriteLine(leagues1.Name);

结果:
在这里插入图片描述
SingleOrDefault和Find的用法如下:

   var leagues1 = context.Leagues.SingleOrDefault(x =>
           x.Id == 1
           ) ;
            var leagues2 = context.Leagues.Find(1
         );
            Console.WriteLine(leagues1?.Name);
            Console.WriteLine(leagues2.Name);

在这里插入图片描述
我们可以看到,查询了2个结果,但只生成了一条SQL语句,这是因为当你执行第一个查询的时候,contex已经做了追踪,所以当你用contex的Find方法进行查询的时候,他会直接先追踪contex内部的内容,如果有就直接进行了查询。相反如果把Find和SingleOrDefault调换位置,就会和数据库进行2次交互。

接着看下LastOrDefault的用法:

  var leagues1 = context.Leagues
                .OrderBy(x => x.Id )
                .LastOrDefault(x=>x.Name.Contains("e"));

这里需要注意一下,使用LastOrDefault后需要进行排序,不然会报错,OrderBy默认为升序,降序用OrderByDescending

5.4删除操作

EFCore只能删除被上下文追踪的内容。也就是说,你每次删除前必须对要删除的数据进行查询,查询后的结果保留在context里面后,EF才可以通过Conetx这个数据库上下文类进行删除操作。
代码如下:

            //只能删除被追踪的数据  先进行查询 才可以删除
           var milan = context.Clubs.Single(x=>x.Name=="AC Milan");
            //调用删除方法   
            // 1.  删除一条数据
            context.Clubs.Remove(milan);
            // 2. 通过context进行自动识别要删除的类进行单条数据删除
            //context.Remove(milan);
            // 3. 批量删除
            //context.Clubs.RemoveRange(milan,milan);
            // 4. contex直接批量删除
            //context.RemoveRange(milan ,milan);
            // 5. 保存操作
            var count = context.SaveChanges();

另外EFcore也支持对存储过程和SQL语句的删除,详细请查阅官方文档。

5.5修改操作

修改操作和删除操作有点像,数据首先需要被Context追踪。才可以进行修改。所追踪的对象,它里面有个状态属性,我们需要把状态设为modify,也就是被修改的意思,然后执行保存的时候,他才会执行这种UPdate这种SQL语句,包括新增删除都有相应的状态
具体代码如下:
对一条数据进行修改操作

    //1. 先执行查询操作,查出来的这个对象 就可以被context进行追踪
            var legue = context.Leagues.First();
            //2. 进行对象里面的Name修改   执行这步操作时候,Context就知道,Name属性被修改了 这时候legue这个状态就是modify  
            legue.Name += "~~";
            //3. 然后执行保存操作,这时候就会把执行的SQL语句执行到数据库
            var count = context.SaveChanges();

对多条数据进行修改操作

    //这句话一般用于分页查询,也就是跳过1条数据。查询2条数据
            var leagues = context.Leagues.Skip(1).Take(2).ToList();

            foreach (var legue in leagues)
            {
                legue.Name += "~~";
            }
            //3. 然后执行保存操作,这时候就会把执行的SQL语句执行到数据库
            var count = context.SaveChanges();
            Console.WriteLine(count);

            Console.ReadKey();

这种通过追踪后,在进行修改的操作,一般都不会符合我们的业务场景,一般前段都是直接传来Json字符串。然后进行反序列化得到对象。这种 情况应该怎么处理呢。直接上代码:

//这就会的意思相当前端传来的数据,进行了反序列化拿到了对象,AsNoTracking 表示不进行对象追踪,也就是和context没关系了。
            var league = context.Leagues.AsNoTracking().First();


            league.Name += "++";

            context.Leagues.Update(league);
          
            //3. 然后执行保存操作,这时候就会把执行的SQL语句执行到数据库
            var count = context.SaveChanges();
            Console.WriteLine(count);

            Console.ReadKey();

Update这个方法就相当于把league这个对象,添加到了context的追踪范围内,然后把d对象的状态设置为modify,最后进行保存操作就可以了。

同样他也有批量修改操作,代码如下


            //批量修改
            context.Leagues.UpdateRange(league);
            //直接通过context直接进行修改
            context.Update(league);
            //批量修改
            context.UpdateRange(league);

修改后我们可以看下修改结果:
在这里插入图片描述
为什么我们只修改了Name,但country也进行了修改。

这是因为,这种离线的数据,他一旦通过Update这种方法进行修改之后,他所有的属性,除了主键,都被设为修改的状态了,就是所有的属性都需要被改一遍,但是我只想修改Name这一个属性的话,这个也是可以做到的。具体操作请查阅官方文档。也可以参考我的另外的文章,JsonPatch的使用

6.添加关系数据

联赛和球队属于一对多关系,球队对队员也是一对多的关系,通过观察模型,我们可以通过一下代码进行实现关联。

  // 1. 查询联赛
            var serieA = context.Leagues.SingleOrDefault(x => x.Name == "Serie A~~++");

            // 2.创建一个新的球队 并且和联赛还有队员进行关联
            var juventus = new Club
            {
                //和联赛进行关联
                League = serieA,
                Name = "juventus",
                City = "Torino",
                DateOfEstablishment = new DateTime(1897, 11, 1),
                //和队员进行关联
                Players = new List<Player>
                {
                    new Player
                    {
                        Name="C.Ronaldo",
                        DateOfBirth=new DateTime(1985,2,5)
                     
                    }
                }
            };

            context.Clubs.Add(juventus);
            int count = context.SaveChanges();
            Console.WriteLine(count);

执行后,可以在数据库查看情况,如图:
在这里插入图片描述
我们可以看到,关联的效果和我们预期的一样。

给现有的球队添加球员,操作如下:

  //1. 查询球队
            var juventus = context.Clubs.SingleOrDefault(x => x.Name== "juventus");
            // 2. 把队员添加到该球队
            juventus.Players.Add(new Player {
            Name="Gonzalo Higuain",
            DateOfBirth=new DateTime(1987,12,10)
            
            });

            int count = context.SaveChanges();
            Console.WriteLine(count);


            Console.ReadKey();

模拟离线数据,对球员进行数据添加,

 //1. 查询球队
            var juventus = context.Clubs.SingleOrDefault(x => x.Name== "juventus");
            // 2. 把队员添加到该球队
            juventus.Players.Add(new Player {
            Name="Matthijs de Ligt",
            DateOfBirth=new DateTime(1999,12,18)
            
            });

            {
                //3. 新建一个contex
                using var newContetx = new DemoContext();
                // 4. 通过新建的上下文对上面需要添加新球员进行修改   上面的1 2 步骤对于新建的数据库上下文就是离线数据 所以要用Update
                newContetx.Clubs.Update(juventus);
                var count = newContetx.SaveChanges();
                Console.WriteLine(count);

            }

结果:在这里插入图片描述
通过日志我们可以看出来,他是先进行对球队里的所有值进行了一下修改操作,然后才进行添加球员操作。这种显然是不太好的。

我们可以通过如下方法解决:
在这里插入图片描述

通过附加的方式进行操作。把Update换成Attach方法。附加之后,所附加的对象是处于未修改的状态,所以说Context不会对该对象进行修改。但是他会发现该对象有个player是没有主键的。所以说他就是一个新增的一个对象。而context就会针对该新增对象生成一个insert语句。然后插入数据库中。

在这里插入图片描述
这次我们可以看到他只执行了一次操作,就是insert操作。

给队员添加一条简历


            var resume = new Resume
            {
                PlayerId=2,
                Description="..."
            };

            context.Resumes.Add(resume);
            var count = context.SaveChanges();
            Console.WriteLine(count);
          

7.加载关系数据

加载方式有如下几种:
在这里插入图片描述

1.预加载

由于查询主表的时候通过Include已经一次性将数据查询了出来,所以在调用从表数据的时候,均从缓存中读取,无须查询数据库。

下面用代码实现一级关联,二级关联和三级关联。以此类推,代码中有对应解释。

    //Include() 指定要包含在查询结果中的相关实体。  也就是和队伍表相关联的外键 导航属性
            var Clubs =   context.Clubs
                 .Where(x => x.Id > 0)//查询条件
                .Include(x => x.League)//关联League表
                .Include(x=>x.Players)//关联Players
                    .ThenInclude(y=>y.Resume)//关联Players下面的Resume  需要用ThenInclude  相当于二级关联
                .Include(x=>x.Players) //club和Player关联
                    .ThenInclude(y=>y.GamePlayers)//Player和GamePlayers关联
                        .ThenInclude(z=>z.Game)//GamePlayer和Game关联,相当于三级关联
                .FirstOrDefault();

context中的Find方法是不支持Include的,Find参数是通过主键查询。

再看下另外一种关联形式

2.显示加载

,单纯查询主表的数据,后面又想再次查询从表,这个时候就需要用到显示加载了。

  //通过Select 实例化一个匿名类,然后把查询出来所需要的结果  映射给实体类。该匿名类相当于一个Dto的过程
            var info =context.Clubs.Where(x => x.Id == 3).Select(x=>new 
            {
            x.Id,
            LeagueName=x.League.Name,
            x.Name,
            Players=x.Players.Where(p=>p.DateOfBirth>new DateTime(1990,1,1))
            }).FirstOrDefault();

注意。这个查询出来的结果,给了一个匿名类 并不是Dbset里面暴露的类,所以context不可以进行变化追踪。他只能追踪被他识别的类、

但是对于Player,这个我们自己定义的Modle 并且在数据库上下文进行了暴露。context他是可以进行变化追踪的。

我们可以通过代码进行试验查看一下:

  var info =context.Clubs.Where(x => x.Id == 3).Select(x=>new 
            {
            x.Id,
            LeagueName=x.League.Name,
            x.Name,
            Players=x.Players.Where(p=>p.DateOfBirth>new DateTime(1990,1,1))
            }).FirstOrDefault();

            for (int i = 0; i < 1; i++)
            {
                foreach (var item in info.Players)
                {
                    item.Name += "1++";
                }
            }
            context.SaveChanges();

打开快速监视窗口 我们通过查看context.ChangesTracker.Entries()这个方法 可以监测到 我们需要修改的属性状态已经变成了modify ,这里解释一下为什么监视这个方法,因为我们自定义的类只要放入了context上下文,他都会被enties这个方法追踪。
在这里插入图片描述
查看数据库也发现名字做了相应的修改
在这里插入图片描述

接着我们换一种方式进行关联
例子:

  var info = context.Clubs.FirstOrDefault();
            context.Entry(info).Collection(x => x.Players)
                .Query()//查询可以通过使用 LINQ 来执行筛选、计数等,而无需实际从数据库加载所有实体。 返回IQueryable类型
                .Where(x=>x.DateOfBirth>new DateTime(1990,1,1))
                .Load(); //Collection针对关联的导航属性是集合形式的

            context.Entry(info).Reference(x => x.League).Load() ;//Reference提供对引用的更改跟踪和加载信息的访问 (即,将此实体与另一实体关联的非集合) 导航属性。

首先是查询了Club这个表,然后对Players进行了关联, 然后关联了League,最后info会得到所有的关联数据。这里注意如果需要对集合进行条件筛选,需要先使用Query()方法把返回结果变成IQueryable 然后再使用Where进行条件过滤。

再看下最后一种形式的

3.懒加载

懒加载一般不怎么使用,使用的话会遇到很多种问题,如果需要使用,请查阅官方文档、也可参考一下其他博主文章EF 的三种加载方式

用子表的属性作为过滤条件:
在这里插入图片描述

接着我们们实现一下多对多关系的查询

如果没有在context中进行暴露的模型,比如中间表,但是又需要用到追踪。可以用一下代码进行实现。

 var gamePlayers = context.Set<GamePlayer>()
                .Where(x => x.Player.Id > 0)
                .Include(x=>x.Player)
                .Include(x=>x.Game)
                .ToList();

通过Set进行暴露。

8.修改关系数据

8.1通过变换追踪的方式,进行关联数据的修改

 var club = context.Clubs.Include(x => x.League)
                .FirstOrDefault();

            club.League.Name += "@";
            context.SaveChanges();

先查询出来关联的数据后,context会自动识别修改关联数据。

在这里插入图片描述
可以看到日志生成的Sql语句。更改了关联的数据。

8.2离线的状态,不采取变换追踪的方式修改关联数据(模拟前端传来的Json数据)。


            //1.多对多的关联   数据查询
            var game = context.Games.Include(x => x.GamePlayers)
                .ThenInclude(x => x.Player)
                .FirstOrDefault();
            //2. 对关联的数据做修改
            var firstPlayer = game.GamePlayers[0].Player;
            firstPlayer.Name += "$";
           
            {
                //重新实例化一个上下文类
                using var newConetx = new DemoContext();

                newConetx.Players.Update(firstPlayer);

                newConetx.SaveChanges();
            }

在这里插入图片描述
我们可以看到,如果直接使用Update的话,我们只修改了一个字段,但是他进行了3次修改,2次game修改和一次player修改

那么如何解决这种问题呢,看一下代码:

    //1.多对多的关联   数据查询
            var game = context.Games.Include(x => x.GamePlayers)
                .ThenInclude(x => x.Player)
                .FirstOrDefault();
            //2. 对关联的数据做修改
            var firstPlayer = game.GamePlayers[0].Player;
            firstPlayer.Name += "$";
           
            {
                //重新实例化一个上下文类
                using var newConetx = new DemoContext();

                //我们手动设置为Modified   这时侯他就只会修改一个firstPlayer这个数据  关联的数据状态不会进行修改
                newConetx.Entry(firstPlayer).State = EntityState.Modified;

                newConetx.SaveChanges();
            }

我们自己手动设置一下Entry的状态就可以了,这样他就只修改了你要修改的那一条数据,其他关联的状态就不会进行修改。

接着来看看如何设置多对多的关系。

 var gameplayer = new GamePlayer
            {
                GameId = 1,
                PlayerId = 3
            };
            context.Add(gameplayer);
            context.SaveChanges();

如果你想设置多对多的关系,如果你知道GameId和PlayerId 的话,直接通过实例化中间表,然后把ID进行赋值,就能让这2张表进行关联。

第二种情况,

 var game = context.Games.FirstOrDefault();
            game.GamePlayers.Add
                (
                              new GamePlayer
                         {
                             PlayerId = 4
                          }
                );

            context.SaveChanges();

game 中有导航属性GamePlayers我们可以直接通过导航属性,给关联表添加信息。因为你是直接在game中通过导航属性给PlayerId 赋值,所以GameId是不需要赋值的。

接着看看如何删除多对多的关系。

 var gamePlayer = new GamePlayer
            {
                GameId = 1,
                PlayerId = 4
            };
            //删除数据
            context.Remove(gamePlayer);
            context.SaveChanges();

修改一对一的关系:

变换追踪
在这里插入图片描述

离线状态下:
在这里插入图片描述
使用Attach后,他会发现player是没有变化的,但是Resume里面实例化了一个Resume,并且他会发现Resume没有主键,所以Attach会自己执行添加操作的语句。

注意,当你第二次执行该操作后,会报异常,因为第二次执行后,最后一名队员,已经添加了简历,此时2张表已经进行了关联,所以你再进行修改操作时候,会报主键异常操作。

在这里插入图片描述
采用关联的方式,他会先把之前的简历删除,然后再进行添加操作。

9.执行原生SQL语句

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐