zoukankan      html  css  js  c++  java
  • EF Core 2.1 支持数据库一对一关系

    在使用EF Core和设计数据库的时候,通常一对多、多对多关系使用得比较多,但是一对一关系使用得就比较少了。最近我发现实际上EF Core很好地支持了数据库的一对一关系。

    数据库


    我们先来看看SQL Server数据库中的表:

    Person表代表的是一个人,表中有些字段来简单描述一个人,其建表语句如下:

    CREATE TABLE [dbo].[Person](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [PersonCode] [nvarchar](50) NULL,
        [Name] [nvarchar](50) NULL,
        [Age] [int] NULL,
        [City] [nvarchar](50) NULL,
        [CreateTime] [datetime] NULL,
     CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
    (
        [ID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
     CONSTRAINT [IX_Person] UNIQUE NONCLUSTERED 
    (
        [PersonCode] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[Person] ADD  CONSTRAINT [DF_Person_CreateTime]  DEFAULT (getdate()) FOR [CreateTime]
    GO

    从上面可以看出,除了主键ID外,我们还设置了列PersonCode为唯一键IX_Person。

    然后数据库中还有张表IdentificationCard,其代表的是一个人的身份证,其中列IdentificationNo是身份证号码,其建表语句如下:

    CREATE TABLE [dbo].[IdentificationCard](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [IdentificationNo] [nvarchar](50) NULL,
        [PersonCode] [nvarchar](50) NULL,
        [CreateTime] [datetime] NULL,
     CONSTRAINT [PK_IdentificationCard] PRIMARY KEY CLUSTERED 
    (
        [ID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
     CONSTRAINT [IX_IdentificationCard] UNIQUE NONCLUSTERED 
    (
        [PersonCode] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[IdentificationCard] ADD  CONSTRAINT [DF_IdentificationCard_CreateTime]  DEFAULT (getdate()) FOR [CreateTime]
    GO
    
    ALTER TABLE [dbo].[IdentificationCard]  WITH CHECK ADD  CONSTRAINT [FK_IdentificationCard_Person] FOREIGN KEY([PersonCode])
    REFERENCES [dbo].[Person] ([PersonCode])
    ON UPDATE CASCADE
    ON DELETE CASCADE
    GO
    
    ALTER TABLE [dbo].[IdentificationCard] CHECK CONSTRAINT [FK_IdentificationCard_Person]
    GO

    其中设置外键关系FK_IdentificationCard_Person:通过IdentificationCard表的PersonCode列来关联Person表的PersonCode列,从而指明一张身份证属于哪个Person。

    然后我们同样设置了IdentificationCard表的PersonCode列为唯一键IX_IdentificationCard,这样外键FK_IdentificationCard_Person表示的实际上就是一对一关系了,因为IdentificationCard表的一行数据通过列PersonCode只能找到一行Person表数据,而现在IdentificationCard表的PersonCode列又是唯一键,所以反过来Person表在IdentificationCard表中最多也只能找到一行数据,所以这是个典型的一对一关系。

    我们还在FK_IdentificationCard_Person外键关系上使用了CASCADE设置了级联删除和级联更新。

    EF Core实体


    接着我们新建了一个.NET Core控制台项目,使用EF Core的Scaffold-DbContext指令自动从数据库中生成实体,可以看到通过我们在数据库中设置的唯一键和外键,EF Core自动识别出了Person表和IdentificationCard表之间是一对一关系,生成的代码如下:

    Person实体,对应的是数据库中的Person表,注意其中包含一个属性IdentificationCard,表示Person表和IdentificationCard表的一对一关系:

    using System;
    using System.Collections.Generic;
    
    namespace FFCoreOneToOne.Entities
    {
        /// <summary>
        /// Person实体,对应数据库中的Person表,可以看到其中有一个IdentificationCard属性,表示Person实体对应一个IdentificationCard实体
        /// </summary>
        public partial class Person
        {
            public int Id { get; set; }
            public string PersonCode { get; set; }
            public string Name { get; set; }
            public int? Age { get; set; }
            public string City { get; set; }
            public DateTime? CreateTime { get; set; }
    
            public IdentificationCard IdentificationCard { get; set; }
        }
    }

    IdentificationCard实体,对应的是数据库中的IdentificationCard表,注意其中包含一个属性PersonCodeNavigation,表示IdentificationCard表和Person表的一对一关系:

    using System;
    using System.Collections.Generic;
    
    namespace FFCoreOneToOne.Entities
    {
        /// <summary>
        /// IdentificationCard实体,对应数据库中的IdentificationCard表,可以看到其中有一个PersonCodeNavigation属性,表示IdentificationCard实体对应一个Person实体
        /// </summary>
        public partial class IdentificationCard
        {
            public int Id { get; set; }
            public string IdentificationNo { get; set; }
            public string PersonCode { get; set; }
            public DateTime? CreateTime { get; set; }
    
            public Person PersonCodeNavigation { get; set; }
        }
    }

    最后是Scaffold-DbContext指令生成的DbContext类TestDBContext,其中比较重要的地方是OnModelCreating方法中,设置IdentificationCard实体和Person实体间一对一关系的Fluent API代码,我用注释详细阐述了每一步的含义:

    using System;
    using FFCoreOneToOne.Logger;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata;
    
    namespace FFCoreOneToOne.Entities
    {
        public partial class TestDBContext : DbContext
        {
            public TestDBContext()
            {
            }
    
            public TestDBContext(DbContextOptions<TestDBContext> options)
                : base(options)
            {
            }
    
            public virtual DbSet<IdentificationCard> IdentificationCard { get; set; }
            public virtual DbSet<Person> Person { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                if (!optionsBuilder.IsConfigured)
                {
                    optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=TestDB");
                }
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<IdentificationCard>(entity =>
                {
                    entity.HasIndex(e => e.PersonCode)
                        .HasName("IX_IdentificationCard")
                        .IsUnique();
    
                    entity.Property(e => e.Id).HasColumnName("ID");
    
                    entity.Property(e => e.CreateTime)
                        .HasColumnType("datetime")
                        .HasDefaultValueSql("(getdate())");
    
                    entity.Property(e => e.IdentificationNo).HasMaxLength(50);
    
                    entity.Property(e => e.PersonCode).HasMaxLength(50);
    
                    //设置IdentificationCard实体和Person实体的一对一关系
                    entity.HasOne(d => d.PersonCodeNavigation)//HasOne设置IdentificationCard实体中有一个Person实体,可以通过IdentificationCard实体的PersonCodeNavigation属性访问到
                        .WithOne(p => p.IdentificationCard)//WithOne设置Person实体中有一个IdentificationCard实体,可以通过Person实体的IdentificationCard属性访问到
                        .HasPrincipalKey<Person>(p => p.PersonCode)//设置数据库中Person表的PersonCode列是一对一关系的主表键
                        .HasForeignKey<IdentificationCard>(d => d.PersonCode)//设置数据库中IdentificationCard表的PersonCode列是一对一关系的从表外键
                        .OnDelete(DeleteBehavior.Cascade)//由于我们在数据库中开启了IdentificationCard表外键FK_IdentificationCard_Person的级联删除,所以这里也生成了实体级联删除的Fluent API
                        .HasConstraintName("FK_IdentificationCard_Person");//设置IdentificationCard实体和Person实体的一对一关系采用的是数据库外键FK_IdentificationCard_Person
                });
    
                modelBuilder.Entity<Person>(entity =>
                {
                    entity.HasIndex(e => e.PersonCode)
                        .HasName("IX_Person")
                        .IsUnique();
    
                    entity.Property(e => e.Id).HasColumnName("ID");
    
                    entity.Property(e => e.City).HasMaxLength(50);
    
                    entity.Property(e => e.CreateTime)
                        .HasColumnType("datetime")
                        .HasDefaultValueSql("(getdate())");
    
                    entity.Property(e => e.Name).HasMaxLength(50);
    
                    entity.Property(e => e.PersonCode)
                        .IsRequired()
                        .HasMaxLength(50);
                });
            }
        }
    }

    示例代码


    接着我们在.NET Core控制台项目的Program类中定义了些示例代码,其中AddPersonWithIdentificationCard和AddIdentificationCardWithPerson方法使用DbContext来添加数据到数据库,RemoveIdentificationCardFromPerson和RemovePersonFromIdentificationCard方法用来演示如何通过实体的导航属性来删除数据,最后DeleteAllPersons是清表语句,删除数据库中IdentificationCard表和Person表的所有数据。

    这里先把示例代码全部贴出来:

    using FFCoreOneToOne.Entities;
    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace FFCoreOneToOne
    {
        class Program
        {
    
            /// <summary>
            /// 删除数据库Person表和IdentificationCard表的所有数据
            /// </summary>
            static void DeleteAllPersons()
            {
                using (TestDBContext dbContext = new TestDBContext())
                {
                    dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[IdentificationCard]");
                    dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");
                }
            }
    
            /// <summary>
            /// 通过添加Person来添加IdentificationCard
            /// </summary>
            static void AddPersonWithIdentificationCard()
            {
                //通过添加Person实体来添加IdentificationCard实体,将Person实体的IdentificationCard属性设置为对应的IdentificationCard实体即可
                using (TestDBContext dbContext = new TestDBContext())
                {
                    var james = new Person() { Name = "James", Age = 30, PersonCode = "P001", City = "Beijing" };
                    james.IdentificationCard = new IdentificationCard() { IdentificationNo = "510100197512305607" };
    
                    var tom = new Person() { Name = "Tom", Age = 35, PersonCode = "P002", City = "Shanghai" };
                    tom.IdentificationCard = new IdentificationCard() { IdentificationNo = "510100197512305609" };
    
                    var sam = new Person() { Name = "Sam", Age = 25, PersonCode = "P003", City = "Chongqing" };
                    sam.IdentificationCard = new IdentificationCard() { IdentificationNo = "510100197512305605" };
    
                    dbContext.Person.Add(james);
                    dbContext.Person.Add(tom);
                    dbContext.Person.Add(sam);
    
                    dbContext.SaveChanges();
                }
            }
    
    
            /// <summary>
            /// 通过添加IdentificationCard来添加Person,从EF Core的日志中可以看到使用这种方式还是先执行的插入Person表数据的SQL,再执行的插入IdentificationCard表数据的SQL
            /// </summary>
            static void AddIdentificationCardWithPerson()
            {
                //通过添加IdentificationCard实体来添加Person实体,将IdentificationCard实体的PersonCodeNavigation属性设置为对应的Person实体即可
                using (TestDBContext dbContext = new TestDBContext())
                {
                    var jamesCard = new IdentificationCard() { IdentificationNo = "510100197512305607" };
                    jamesCard.PersonCodeNavigation = new Person() { Name = "James", Age = 30, PersonCode = "P001", City = "Beijing" };
    
                    var tomCard = new IdentificationCard() { IdentificationNo = "510100197512305609" };
                    tomCard.PersonCodeNavigation = new Person() { Name = "Tom", Age = 35, PersonCode = "P002", City = "Shanghai" };
    
                    var samCard = new IdentificationCard() { IdentificationNo = "510100197512305605" };
                    samCard.PersonCodeNavigation = new Person() { Name = "Sam", Age = 25, PersonCode = "P003", City = "Chongqing" };
    
                    dbContext.IdentificationCard.Add(jamesCard);
                    dbContext.IdentificationCard.Add(tomCard);
                    dbContext.IdentificationCard.Add(samCard);
    
                    dbContext.SaveChanges();
                }
            }
    
            /// <summary>
            /// 通过设置Person实体的IdentificationCard属性为null来删除IdentificationCard表的数据
            /// </summary>
            static void RemoveIdentificationCardFromPerson()
            {
                //先用DbContext从数据库中查询出Person实体,然后设置其IdentificationCard属性为null,来删除IdentificationCard表的数据
                //注意在查询Person实体的时候,记得要用EF Core中Eager Loading的Include方法也查询出IdentificationCard实体,这样我们在设置Person实体的IdentificationCard属性为null后,DbContext才能跟踪到变更,才会在下面调用DbContext.SaveChanges方法时,生成删除IdentificationCard表数据的SQL语句
                using (TestDBContext dbContext = new TestDBContext())
                {
                    var james = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "James");
                    james.IdentificationCard = null;
    
                    var tom = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "Tom");
                    tom.IdentificationCard = null;
    
                    var sam = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "Sam");
                    sam.IdentificationCard = null;
    
                    dbContext.SaveChanges();
                }
            }
    
            /// <summary>
            /// 本来这个方法是想用来通过设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据,但是结果是还是删除的IdentificationCard表数据
            /// </summary>
            static void RemovePersonFromIdentificationCard()
            {
                //原本我想的是,先用DbContext从数据库中查询出IdentificationCard实体,并用EF Core中Eager Loading的Include方法也查询出Person实体,然后设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据
                //结果这样做EF Core最后还是删除的IdentificationCard表的数据,原因是IdentificationCard表是一对一外键关系的从表,设置从表实体的外键属性PersonCodeNavigation为null,EF Core认为的是从表的数据作废,所以删除了从表IdentificationCard中的数据,主表Person的数据还在。。。
                using (TestDBContext dbContext = new TestDBContext())
                {
                    var jamesCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305607");
                    jamesCard.PersonCodeNavigation = null;
    
                    var tomCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305609");
                    tomCard.PersonCodeNavigation = null;
    
                    var samCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305605");
                    samCard.PersonCodeNavigation = null;
    
                    dbContext.SaveChanges();
                }
            }
    
            static void Main(string[] args)
            {
                DeleteAllPersons();
    
                AddPersonWithIdentificationCard();
                AddIdentificationCardWithPerson();
                RemoveIdentificationCardFromPerson();
                RemovePersonFromIdentificationCard();
    
                Console.WriteLine("Press any key to quit...");
                Console.ReadKey();
            }
        }
    }

    AddPersonWithIdentificationCard

    首先我们测试AddPersonWithIdentificationCard方法,其通过添加Person实体到数据库来添加IdentificationCard表的数据,更改Main方法的代码如下,并执行程序:

    static void Main(string[] args)
    {
        DeleteAllPersons();
    
        AddPersonWithIdentificationCard();
        //AddIdentificationCardWithPerson();
        //RemoveIdentificationCardFromPerson();
        //RemovePersonFromIdentificationCard();
    
        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

    执行后数据库中Person表的数据如下:

    IdentificationCard表的数据如下:

    AddIdentificationCardWithPerson

    然后我们测试AddIdentificationCardWithPerson方法,其通过添加IdentificationCard实体到数据库来添加Person表的数据,从EF Core的日志中可以看到使用这种方式还是先执行的插入Person表数据的SQL,再执行的插入IdentificationCard表数据的SQL。更改Main方法的代码如下,并执行程序:

    static void Main(string[] args)
    {
        DeleteAllPersons();
    
        //AddPersonWithIdentificationCard();
        AddIdentificationCardWithPerson();
        //RemoveIdentificationCardFromPerson();
        //RemovePersonFromIdentificationCard();
    
        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

    执行后数据库中Person表的数据如下:

    IdentificationCard表的数据如下:

    RemoveIdentificationCardFromPerson

    然后我们测试RemoveIdentificationCardFromPerson方法,其通过设置Person实体的IdentificationCard属性为null,来删除IdentificationCard表的数据,更改Main方法的代码如下,并执行程序:

    static void Main(string[] args)
    {
        DeleteAllPersons();
    
        AddPersonWithIdentificationCard();
        //AddIdentificationCardWithPerson();
        RemoveIdentificationCardFromPerson();
        //RemovePersonFromIdentificationCard();
    
        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

    执行后数据库中Person表的数据如下:

    IdentificationCard表的数据如下:

    RemovePersonFromIdentificationCard

    最后我们测试RemovePersonFromIdentificationCard方法,本来这个方法我是设计用来通过设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据,但是测试后发现结果还是删除的IdentificationCard表的数据,原因可以看下上面示例代码中RemovePersonFromIdentificationCard方法中的注释。更改Main方法的代码如下,并执行程序:

    static void Main(string[] args)
    {
        DeleteAllPersons();
    
        AddPersonWithIdentificationCard();
        //AddIdentificationCardWithPerson();
        //RemoveIdentificationCardFromPerson();
        RemovePersonFromIdentificationCard();
    
        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

    执行后数据库中Person表的数据如下:

    IdentificationCard表的数据如下:

  • 相关阅读:
    java时间戳与Date相互转换、日期格式化、给日期加上指定时长、判断两时间点是否为同一天
    notepad++去掉红色波浪线
    发生异常Address already in use: bind
    SecureCRT背景颜色
    linux查看实时日志命令
    idel上传代码到github时遇到的Push rejected: Push to origin/master was rejected
    git解决error: The following untracked working tree files would be overwritten by checkout
    使用SecureCRT工具上传、下载文件的两种方法
    Windows下Zookeeper启动zkServer.cmd闪退问题的解决方案
    Maven的Snapshot版本与Release版本
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9810653.html
Copyright © 2011-2022 走看看