日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

EntityFramework Core映射关系详解

發布時間:2025/3/15 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EntityFramework Core映射关系详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

Hello,開始回歸開始每周更新一到兩篇博客,本節我們回歸下EF Core基礎,來講述EF Core中到底是如何映射的,廢話少說,我們開始。

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則為如下:

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語句。如下:

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創建了索引,如下:

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

由上知,對于一對多關系中的外鍵,EF Core會默認創建其索引,當然這里的索引肯定是非唯一非聚集索引,聚集索引為其主鍵。我們通過數據庫上就可以看到,如下:

此時即使我們不配置指定外鍵為BlogId同樣也沒毛病,如下:

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

因為上述我們已經明確寫出了BlogId,但是EF Core依然可以為其指定BlogId為外鍵,現在我們反過來想,要是我們將Post中的BlogId刪除,同樣進行上述映射是否好使呢,經過實際驗證確實是可以的,如下:

別著急下結論,我們再來看一種情況,現在我們進行如下配置并除去Post中的BlogId還是否依然好使呢?

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

總結

以上是生活随笔為你收集整理的EntityFramework Core映射关系详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。