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表的數據如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM