補知識:EntityFramework Core映射關係詳解

前言

本節咱們迴歸下EF Core基礎,來說述EF Core中究竟是如何映射的,廢話少說,咱們開始。html

One-Many Relationship(一對多關係)

首先咱們從最簡單的一對多關係提及,咱們給出須要映射的兩個類,一個是Blog,另一個則是Post,以下:數據庫

    public class Blog
    {
        public int Id { get; set; }
        public int Count { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public IEnumerable<Post> Posts { get; set; }

    }
    public class Post
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual string Content { get; set; }

        public virtual int BlogId { get; set; }
        public virtual Blog Blog { get; set; }
    }

此時咱們從Blog來看,一個Blog下對應多個Post,而一個Post對應只屬於一個Blog,此時配置關係以下:併發

     public class BlogMap : EntityMappingConfiguration<Blog>
    {
        public override void Map(EntityTypeBuilder<Blog> b)
        {
            b.ToTable("Blog");
            b.HasKey(k => k.Id);

            b.Property(p => p.Count);
            b.Property(p => p.Url);
            b.Property(p => p.Name);
            b.HasMany(p => p.Posts) .WithOne(p => p.Blog) .HasForeignKey(p => p.BlogId);
        }
    }

而Post則爲以下:app

    public class PostMap : EntityMappingConfiguration<Post>
    {
        public override void Map(EntityTypeBuilder<Post> b)
        {
            b.ToTable("Post");
            b.HasKey(k => k.Id);
            b.Property(p => p.Title);
            b.Property(p => p.Content);
        }
    }

此時咱們利用SqlProfiler監控生成的SQL語句。以下:ide

CREATE TABLE [Blog] (
    [Id] int NOT NULL IDENTITY,
    [Count] int NOT NULL,
    [Name] nvarchar(max),
    [Url] nvarchar(max),
    CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
);
CREATE TABLE [Post] (
    [Id] int NOT NULL IDENTITY,
    [BlogId] int NOT NULL,
    [Content] nvarchar(max),
    [Title] nvarchar(max),
    CONSTRAINT [PK_Post] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([Id]) ON DELETE CASCADE
);

此時咱們可以很明確的看到對於Post表上的BlogId創建外鍵BlogId,也就是對應的Blog表上的主鍵即Id,同時後面給出了DELETE CASADE即進行級聯刪除的標識,也就是說當刪除了Blog上的數據,那麼此時Post表上對應的數據也會進行相應的刪除。同時在生成SQL語句時,還對Post上的BlogId建立了索引,以下:post

CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);

由上知,對於一對多關係中的外鍵,EF Core會默認建立其索引,固然這裏的索引確定是非惟一非彙集索引,彙集索引爲其主鍵。咱們經過數據庫上就能夠看到,以下:ui

此時即便咱們不配置指定外鍵爲BlogId一樣也沒毛病,以下:spa

b.HasMany(m => m.Posts).WithOne(o => o.Blog);

由於上述咱們已經明確寫出了BlogId,可是EF Core依然能夠爲其指定BlogId爲外鍵,如今咱們反過來想,要是咱們將Post中的BlogId刪除,一樣進行上述映射是否好使呢,通過實際驗證確實是能夠的,以下:code

彆着急下結論,咱們再來看一種狀況,如今咱們進行以下配置併除去Post中的BlogId仍是否依然好使呢?htm

b.HasMany(m => m.Posts);

通過臨牀認證,也是好使的,可以正確表達咱們想要的效果並自動添加了外鍵BlogId列,因此到這裏咱們能夠爲一對多關係下個結論:

一對多關係結論

在一對多關係中,咱們能夠經過映射明確指定外鍵列,也能夠不指定,由於EF Core內部會查找是否已經指定其外鍵列有則直接用指定的,沒有則自動生成一個外鍵列,列名爲外鍵列所在的類名+Id。同時對於一對多關係咱們能夠直接只使用HasMany方法來配置映射而不須要再配置HasOne或者WithOne,上述皆是從正向角度去配置映射,由於易於理解,固然反之亦然。

One-One RelationShip (一對一關係)

對於一對一關係和多對多關係稍微複雜一點,咱們來各個擊破,咱們經過舉例好比一個產品只屬於一個分類,而一個分類下只有一個產品,以下:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Category Category { get; set; }
    }
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; } }

此時咱們來進行一下一對一關係映射從產品角度出發:

    public class ProductMap : EntityMappingConfiguration<Product>
    {
        public override void Map(EntityTypeBuilder<Product> b)
        {
            b.ToTable("Product");
            b.HasKey(k => k.Id);

            b.HasOne(o => o.Category).WithOne(o => o.Product);
        }
}

此時咱們經過 dotnet ef migrations add Initial 初始化就已經出現以下錯誤:

大概意思爲未明確Product和Category誰是依賴項,未明確指定致使出現上述錯誤。而上述對於一對多關係則不會出現如此錯誤,仔細分析不難發現一對多已經明確誰是主體,而對於一對一關係兩者爲一一對應關係,因此EF Core沒法判斷其主體,因此必須咱們手動去指定。此時咱們若進行以下指定你會發現沒有lambda表達式提示:

 b.HasOne(o => o.Category)
                .WithOne(o => o.Product)
                .HasForeignKey(k=>k.)

仍是由於主體關係的緣由,咱們仍是必須指定泛型參數才能夠。以下所示:

 b.HasOne(o => o.Category)
                .WithOne(o => o.Product)
                .HasForeignKey<Category>(k => k.ProductId);

此時在Category上建立ProductId外鍵,同時會對ProductId建立以下的惟一非彙集索引:

CREATE UNIQUE INDEX [IX_Category_ProductId] ON [Category] ([ProductId]);

Many-Many RelationShip (多對多關係)

多對多關係在EF Core以前版本有直接使用的方法如HasMany-WithMany,可是在EF Core中則再也不提供對應的方法,想一想多對多關係仍是能夠經過一對多能夠獲得,好比一個產品屬於多個分類,而一個分類對應多個產品,典型的多對多關係,可是經過咱們的描述則徹底能夠經過一對多關係而映射獲得,下面咱們一塊兒來看看:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int ProductId { get; set; }
        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }
    public class ProductCategory
    {
        public int ProductId { get; set; }
        public Product Product { get; set; }

        public int CategoryId { get; set; }
        public Category Category { get; set; }
    }

上述咱們將給出第三個關聯類即ProductCategory,將Product(產品類)和Category(分類類)關聯到ProductCategory類,最終咱們經過ProductCategory來進行映射,以下:

    public class ProductCategoryMap : EntityMappingConfiguration<ProductCategory>
    {
        public override void Map(EntityTypeBuilder<ProductCategory> b)
        {
            b.ToTable("ProductCategory");

            b.HasKey(k => k.Id);

            b.HasOne(p => p.Product)
                .WithMany(p => p.ProductCategorys)
                .HasForeignKey(k => k.ProductId);

            b.HasOne(p => p.Category)
                .WithMany(p => p.ProductCategorys)
                .HasForeignKey(k => k.CategoryId);
        }
    }

好了到了這裏爲止,關於三種映射關係咱們介紹完了,是否是就此結束了,遠遠不是,下面咱們再來其餘屬性映射。

鍵映射

關於鍵映射中的外鍵映射上述已經討論過,下面咱們來說講其餘類型鍵的映射。

備用鍵/可選鍵映射(HasAlternateKey)

備用鍵/可選鍵能夠爲一個實體類配置除主鍵以外的惟一標識,好比在登陸中用戶名能夠做爲用戶的惟一標識除了主鍵標識外,這個時候咱們能夠爲UserName配置可選鍵,打個比方這樣一個場景:一個用戶只能購買一本書,在Book表中配置一個主鍵和用戶Id(例子雖然不太恰當卻能很好描述可選鍵的使用場景)

    public class Book
    {
        public int Id { get; set; }
        public string UserId { get; set; }
    }

下面咱們經過可選鍵來配置用戶Id的映射

    public class BookMap : EntityMappingConfiguration<Book>
    {
        public override void Map(EntityTypeBuilder<Book> b)
        {
            b.ToTable("Book");
            b.HasKey(k => k.Id);
            b.HasAlternateKey(k => k.UserId);
        }
    }

最後監控獲得以下語句:

看到沒,爲用戶Id配置了惟一約束:

CONSTRAINT [AK_Book_UserId] UNIQUE ([UserId])

因此咱們得出結論:經過可選鍵咱們能夠建立惟一約束來除主鍵以外惟一標識行。

主體鍵映射(Principal Key) 

若是咱們想要一個外鍵引用一個屬性而不是主鍵,此時咱們能夠經過主體鍵映射來進行配置,此時配置主體鍵映射背後實際上自動將其設置爲一個可選鍵。這個就不用咱們多講了。

好了到此爲止咱們講完了鍵映射,接下來咱們再來說述屬性映射:

屬性映射

對於C#中string類型若咱們不進行配置,那麼在數據庫中將默認設置爲NVARCHAR而且長度爲MAX且是爲可空,以下:

若咱們須要設置其長度且爲非空,此時須要進行以下配置:

b.Property(p => p.Name).IsRequired().HasMaxLength(50);

經過HaxMaxLength方法來指定最大長度,經過IsRequired方法來指定爲非空。可是此時問題來了,數據庫類型對於string有VARCHAR、CHAR、NCAHR類型,那麼咱們應當如何映射呢?好比對於VARCHAR類型,在EF Core中對於數據庫列類型咱們能夠經過 HasColumnType 方法來進行映射,那麼假設對於數據庫類型爲VARCHAR長度爲50且爲非空,咱們是否能夠進行以下映射呢?

         b.Property(p => p.Name)
                .IsRequired()
                .HasColumnType("VARCHAR")
                .HasMaxLength(50);

經過上述遷移出錯,咱們修改爲以下才正確:

b.Property(p => p.Name)
                .IsRequired()
                .HasColumnType("VARCHAR(50)");

解決一個,又來一個,那麼對於枚舉類型咱們又該進行如何映射呢,枚舉對應數據庫中的類型爲TINYINT,咱們進行以下設置:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Type Type { get; set; }
        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }

    public enum Type
    {
        [Description("普通")]
        General = 0,
        [Description("保險")]
        Insurance = 1
    }
    public class ProductMap : EntityMappingConfiguration<Product>
    {
        public override void Map(EntityTypeBuilder<Product> b)
        {
            b.ToTable("Product");
            b.HasKey(k => k.Id);

            b.Property(p => p.Type)
                .IsRequired()
                .HasColumnType("TINYINT");
        }
    }

此時則對應生成咱們想要的類型:

CREATE TABLE [Product] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max),
    [Type] TINYINT NOT NULL,
    CONSTRAINT [PK_Product] PRIMARY KEY ([Id])

【注意】:此時將其映射成枚舉沒毛病上述已經演示,可是當咱們獲取數據時將TINYINT轉換成枚舉時將出現以下錯誤:

說到底TINYINT對應C#中的byte類最後嘗試將其轉換爲int纔會致使轉換失敗,因此在定義枚舉時記得將其繼承自byte,以下才好使:

    public enum Type : byte
    {
        [Description("普通")]
        General = 0,
        [Description("保險")]
        Insurance = 1
    }

講完如上映射後,咱們再來說講默認值映射。 當咱們敲默認映射會發現有兩個,一個是HasDefaultValue,一個是HasDefaultValueSql,咱們一塊兒來看看到底如何用:

咱們在Product類中添加Count字段:

 public int Count { get; set; }
b.Property(p => p.Count).HasDefaultValue(0);

如上是對於int類型如上設置,若是是枚舉類型呢,咱們來試試:

 b.Property(p => p.Type)
                .IsRequired()
                .HasColumnType("TINYINT").HasDefaultValue(0);

此時遷移將出現以下錯誤:

也就是說沒法將枚舉值設置成int類型,此時咱們應該利用HasDefaultValueSql來映射:

  b.Property(p => p.Type)
                .IsRequired()
                .HasColumnType("TINYINT").HasDefaultValueSql("0");

對於默認值映射總結起來就一句話:對於C#中的類型和數據庫類型一致的話用HasDefaultValue,不然請用HasDefaluValueSql。

【注意】:對於字段類型映射有一個奇葩特例,對於日期類型DateTime,在數據庫中也存在其對應的類型datetime,可是若是咱們不手動指定類型會默認映射成更精確的日期類型即datetime2(7)。

咱們在Product類中添加建立時間列,以下:

        public DateTime CreatedTime { get; set; }

此時咱們不指定其映射類型,此時咱們看到在數據庫中的類型爲datetime2(7)

固然以上映射也沒什麼問題,可是對於大部分對於日期類型都是映射成datetime且給定默認時間爲當前時間,因此此時須要手動進行配置,以下:

b.Property(p => p.CreatedTime)
                .HasColumnType("DATETIME")
                .HasDefaultValueSql("GETDATE()");

說完默認值須要注意的問題,咱們再來說講計算列的映射,在EF Core中對於計算列映射,在以前版本爲ForSqlServerHasComputedColumnSql,目前是HasComputedColumnSql。例如以下這是計算列:

 b.Property(p => p.Name)
                .IsRequired()
                .HasComputedColumnSql("((N'Cnblogs'+CONVERT([CHAR](8),[CreatedTime],(112)))+RIGHT(REPLICATE(N'0',(6))+CONVERT([NVARCHAR],[Id],(0)),(6)))");

 

其中還有關於列名自定義的方法(HasColumnName),主鍵是否自動生成(ValueGeneratedOnAdd)等方法以及行版本(IsRowVersion)和併發Token(IsConcurrencyToken)。還有設置索引的方法HasIndex

這裏有一個疑問對於string默認設置是爲NVARCHAR,其就是unicode,不知爲什麼還有一個IsUnicode方法,它不也是設置爲NVARCHAR的嗎,這是什麼狀況?求解,當咱們同時設置IsUnicode方法和列類型爲VARCHAR時,則仍是會生成NVARCHAR,可見映射成NVARCHAR優先級比VARCHAR高,以下

 b.Property(p => p.Name)
                .IsRequired().IsUnicode()
                .HasColumnType("VARCHAR(21)")
                .HasComputedColumnSql("((N'Cnblogs'+CONVERT([CHAR](8),[CreatedTime],(112)))+RIGHT(REPLICATE(N'0',(6))+CONVERT([NVARCHAR],[Id],(0)),(6)))");

總結 

本文大概就稍微講解了EF Core中的映射以及一些稍微注意的地方,恰好今天父親節,在此祝願天下父母健康長壽,咱們下節再會!

 

 
 
 

 

來自:https://www.cnblogs.com/CreateMyself/p/6995403.html

相關文章
相關標籤/搜索