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表的数据如下:

  • 相关阅读:
    UVa 10118 记忆化搜索 Free Candies
    CodeForces 568B DP Symmetric and Transitive
    UVa 11695 树的直径 Flight Planning
    UVa 10934 DP Dropping water balloons
    CodeForces 543D 树形DP Road Improvement
    CodeForces 570E DP Pig and Palindromes
    HDU 5396 区间DP 数学 Expression
    HDU 5402 模拟 构造 Travelling Salesman Problem
    HDU 5399 数学 Too Simple
    CodeForces 567F DP Mausoleum
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9810653.html
Copyright © 2011-2022 走看看