EntityFramework Core 2.1從新梳理系列屬性映射(一)

前言

滿血復活啦,大概有三個月的時間沒更新博客了,關於EF Core最新進展這三個月也沒怎麼去看,不知現階段有何變化沒,本文將以EF Core 2.1穩定版本做爲從新梳理系列,但願對看本文的你有所幫助,歡迎一塊兒探討。(請不要嫌棄囉嗦哈,我習慣於未來龍去脈給你們梳理清楚,各類我能想到的場景給你們講解明白)。html

屬性映射探討

當咱們利用Code First映射屬性時,此時自己沒有什麼太大問題,可是當咱們初始化表或者獲取數據時等等,經過日誌會發現打印出一些須要咱們注意的地方,推薦咱們使用最佳方式,對於屬性探討咱們將着眼於進一步探討日誌中所打印的信息。咱們依然利用兩個類Blog和Post來探討,你們也好對照着看。node

    public class Blog
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public byte Status { get; set; }
        public bool Deleted { get; set; }
        public DateTime CreatedTime { get; set; }

        public ICollection<Post> Posts { get; set; }
    }

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

首先咱們在映射時,不給定屬性默認值以及映射列類型等,直接看看遷移時生成的列類型是怎樣,而後咱們再來進一步深刻。對於關係映射仍是建議手動配置一下,雖然EF Core也會經過約定來自動進行配置,可是手動配置便於理解,以下:數據庫

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(b => 
            {
                b.ToTable("Blogs");

                b.HasMany(m => m.Posts)
                    .WithOne(o => o.Blog)
                    .HasForeignKey(k => k.BlogId);
            });

            modelBuilder.Entity<Post>(b =>
            {
                b.ToTable("Posts");
            });
        }

 

以上遷移是EF Core默認根據約定生成列類型以及約束和級聯刪除的狀況。屬性Id做爲主鍵且自增加,對於字符串默認建立爲NVARCHAR且長度爲max,同時可空。日期類型默認爲DATETIME2。這些都是最基礎的東西,在我寫的書中也有詳細介紹,就再也不囉嗦了。咱們從如下幾點開始探討。less

主鍵映射並添加數據探討

數據庫主鍵列無外乎就是INT、BIGINT、GUID、VARCHAR(36)這幾種常見類型,接下來咱們一一探討。對於INT或者BIGINT整數類型大多數狀況下,咱們的主鍵都是數據庫自動生成即自增加,因此此時咱們進行以下操做萬無一失。ide

            var blog = new Blog()
            {
                CreatedTime = Convert.ToDateTime("2018-10-20"),
                Deleted = false,
                Status = 1,
                Name = "EFCore"
            };
            _context.Blogs.Add(blog);
            var result = _context.SaveChanges();

上述咱們也說過咱們並未設置主鍵是否自增加,若是不進行手動配置,這個根據默認約定而配置。固然對於主鍵如果客戶端自動生成,咱們只需進行以下映射便可。ui

                b.HasKey(k => k.Id);
                b.Property(p => p.Id).ValueGeneratedNever();

我我的比較習慣對於主鍵也手動經過HasKey進行配置。在添加數據時根據約定主鍵自動增加,如上述。固然咱們也能夠手動配置在添加仍是更新時自增加,以下:spa

                b.Property(p => p.Id).ValueGeneratedOnAdd();
                b.Property(P => P.Id).ValueGeneratedOnAddOrUpdate();
                b.Property(p => p.Id).ValueGeneratedOnUpdate();

是否是就這麼完了呢?其實遠沒有這麼簡單,咱們只看上述第一個即 ValueGeneratedOnAdd ,咱們去看其方法解釋。3d

        // 摘要:
        //     Configures a property to have a value generated only when saving a new entity,
        //     unless a non-null, non-temporary value has been set, in which case the set value
        //     will be saved instead. The value may be generated by a client-side value generator
        //     or may be generated by the database as part of saving the entity.

此方法解釋若是主鍵爲非空或者沒有臨時值未被設置的話,數據庫將自動生成主鍵。同時請注意,它也代表主鍵值在保存時可經過客戶端或者數據庫自動生成。咱們剛纔演示了在數據庫自動生成,既然解釋在客戶端也可自動生成,那咱們再來添加一條數據試試呢。日誌

            var blog = new Blog()
            {
                Id = 2,
                CreatedTime = Convert.ToDateTime("2018-10-20"),
                Deleted = false,
                Status = 1,
                Name = "EFCore"
            };
            _context.Blogs.Add(blog);
            var result = _context.SaveChanges();

 

有關EF 6.x和EF Core插入數據不一樣,請參看此連接:http://www.javashuo.com/article/p-uerkgmpc-gc.html。該方法明確說好的在客戶端也可自動生成的啊,難道解釋有誤?在EF 6.x中默認打開了IDENTITY_INSERT,而在EF Core中將IDENTITY_INSERT給關閉了,因此咱們要想始終使用自增加值即便客戶端給定了值且不拋出異常,那麼須要在數據庫中將IDENTITY_INSERT給打開才行。在EF Core會遇到將主鍵設置成臨時值的狀況,可是若是咱們又不想顯式打開IDENTITY_INSERT,同時須要始終使用自增加值,那麼該如何作呢?在EF 6.x中能夠經過 HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) 進行設置,在EF Core則不能進行全局設置。這個問題的出現仍是dudu老大所提出的,最終我所給出的答案則是利用追蹤圖來解決(https://q.cnblogs.com/q/110205/),追蹤圖中可獲取到對象全部狀態和主鍵是否已經設置,因此最終解決方案以下:code

            _context.ChangeTracker.TrackGraph(blog, node =>
            {
                if (node.Entry.IsKeySet)
                {
                    blog.Id = 0;
                    _context.Blogs.Add(blog);
                }
            });

到了這裏,還有一個方法咱們一直不曾講解到,不知你是否在項目中應用過,在EF Core還有一個 UseSqlServerIdentityColumn 方法,其名爲使用SQL Server標識列,該方法只能針對Key進行設置,最終則會調用 ValueGeneratedOnAdd 方法,如此而已。

 

對於主鍵咱們只是講解了INT類型,對於BIGINT和INT同樣都是整數,那咱們看看帶小數點的,好比主鍵爲decimal類型。下面咱們來改變一下上述Blog表主鍵Id的數據類型。修改成decimal,同時Post類中的外鍵BlogId也修改成decimal,且手動配置添加 ValueGeneratedOnAdd 方法,以下:

  public decimal Id { get; set; }

此時咱們再來從新遷移一下。

經過上述錯誤咱們知道主鍵列的數據類型,同時呢也知道 ValueGeneratedOnAdd 方法手動配置只是針對於整數類型,對於小數類型,則不能應用其方法,咱們去掉該方法再看看。

由於其帶小數,因此此時EF Core會自動發現主鍵且非自增加,這和咱們在數據庫正常設置主鍵和是否自增加一致。下面咱們再來看看主鍵爲GUID的狀況,將主鍵修改成GUID以下:

  public Guid Id { get; set; }

若是主鍵是GUID,那麼對於數據庫列類型就是 uniqueidentifier 。此時也就涉及到兩種狀況,一是客戶端自動生成,二是數據庫自動生成。默認狀況下會在數據庫自動生成GUID。若是咱們手動設置了GUID,那麼將以手動設置的GUID爲準,以下:

            var blog = new Blog()
            {
                Id = Guid.Parse("13D375A1-8AE7-4B84-B220-6BAB72FA2454"),
                CreatedTime = Convert.ToDateTime("2018-10-20"),
                Deleted = false,
                Status = 1,
                Name = "EFCore"
            };

            _context.Blogs.Add(blog);
            var result = _context.SaveChanges();

如果咱們同時配置了添加時自動生成和數據庫自動生成,此時在添加時倒是給定了其值,此時依然是添加的爲準,以下:

   b.Property(p => p.Id).HasDefaultValueSql("NEWID()");
   b.Property(p => p.Id).ValueGeneratedOnAdd();

講完主鍵爲GUID類型,咱們再來說講主鍵列類型爲VARCHAR(36),咱們要使其在添加時自動生成,進行以下設置便可,EF Core會自動發現並生成36位的字符串,無需配置 ValueGeneratedOnAdd 方法。

   b.Property(p => p.Id).HasColumnType("VARCHAR(36)");

雖然咱們一直說對於字符串類型映射,默認映射爲可空,可是主鍵不一樣,主鍵原本就不可空,因此上述咱們設置主鍵爲VARCHAR(36),無需畫蛇添足設置 IsRequired ,可是須要注意的是此時對於外鍵BlogId,依據關係是否必須,若是必須必定要設置 IsRequired ,不然爲可空類型。若進行以下設置數據庫自動生成,同時客戶端手動指定了主鍵,則以手動指定爲準。

 b.Property(p => p.Id).HasDefaultValueSql("NEWID()");

以上講了這麼多,咱們來對主鍵映射作一個完整的總結。

(1)對於Int、Int64等整數,默認狀況下自增加即數據庫自動生成,添加數據時若是主鍵爲空或者爲0,數據庫將自動生成,不然拋出異常。而對於decimal小數,主鍵Id由客戶端指定生成。

(2) 對於Guid類型,默認狀況下數據庫自動生成,無需顯式調用ValueGeneratedOnAdd方法或者HasDefaultValueSql("NEWID()"),若顯式指定Guid,將會覆蓋數據庫自動生成。

(3)對於VARCHAR(36)類型,默認狀況下自動生成,無需顯式調用ValueGeneratedOnAdd方法或者HasDefaultValueSql("NEWID()"),若顯式指定36位字符串,將會覆蓋數據庫自動生成。

初始化默認值探討(何時用HasDefaultValue和HasDefaultValueSql)

默認值最多見類型屬於bool、byte、datetime等等。同時提供默認咱們可經過HasDefaultValue和HasDefaultValueSql兩個方法來進行,那麼是否是兩者使用沒有什麼異同呢?下面咱們一一來探討,在Blog類中有Deleted屬性,咱們映射其默認值爲false,以下:

 b.Property(p => p.Deleted).HasDefaultValue(false);

 //或者

 b.Property(p => p.Deleted).HasDefaultValueSql("0");

當咱們遷移時會發現以下日誌:

布爾類型默認值原本就是false,因此上述徹底不用顯式去配置,如此配置畫蛇添足並且在日誌中還會打印一條warning消息建議使用可空類型布爾值。對於布爾類型,不用映射時給定默認值,若是該類型爲可空,將其屬性設爲可空便可。那麼要是咱們設置默認值不爲false而是true呢?下面咱們再來看看。

  b.Property(p => p.Deleted).HasDefaultValue(true);

此時遷移時依然會打印上述警示信息,在應用程序日誌中也會顯示這些警示信息,這個警示我認爲有點讓人疑惑,應該改善提示信息,可是咱們真的不想根據其建議設置爲可空類型,沒什麼太大意義。此時咱們應該怎麼辦呢?咱們能夠進行以下映射配置從而警示信息也不會再有:

  b.Property(p => p.Deleted).HasDefaultValue(true).ValueGeneratedNever();

上述經過ValueGeneratedNever方法配置意在代表:

當遷移時對數據庫中已存在的行使用其配置的默認值,而對新增的數據行徹底不使用其默認值,換句話說,告知EF Core即便配置了默認值,在EF Core運行時也不該該使用其默認值。

下面咱們再來看看byte映射爲列tinyint狀況。對於上述Status屬性,咱們進行以下映射。

  b.Property(k => k.Status).HasDefaultValue(0);

 

接下來咱們修改成HasDefaultValueSql,以下:

   b.Property(k => k.Status).HasDefaultValueSql("0");

 

咱們看到在使用HasDefaultValue和HasDefaultValueSql的區別所在,對於byte映射時利用HasDefaultValue會存在轉換的狀況(注意:在EF Core 2.0中利用HasDefaultValue不存在轉換問題和HasDefaultValueSql配置一致),因此此時只能利用HasDefaultValueSql來映射默認值。對於日期列類型大部分狀況都是DateTime,此時咱們也只能經過HasDefaultValueSql來指定默認值,以下:

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

上述咱們對Status和DateTime指定了默認值,咱們在添加數據時,依然指定Status默認值爲0,CreatedTime也指定時間看看。

            var blog = new Blog()
            {
                Status = 0,
                CreatedTime = Convert.ToDateTime("2018-10-30"),
                Name = "EFCore",
            };

            _context.Blogs.Add(blog);
            var result = _context.SaveChanges();

 

在此咱們對屬性指定默認值作一個完整的總結:

(1)若是指定默認值和CLR Type默認值一致,此時數據庫列默認值即CLR type默認值,同時咱們無需經過HasDefaultValue和HasDefaultValueSql方法顯式配置默認值,無疑是畫蛇添足且會在日誌打印warning信息。不然須要經過HasDefaultValue和HasDefaultValueSql顯式指定默認值。

(2)對於HasDefaultValue和HasDefaultValueSql方法顯式配置默認值時需注意值類型是否和數據庫列類型一致,若是一致用HasDefaultValue方法,不然請用HasDefaultValueSql方法。如若否則會存在默認值顯式轉換的狀況。

(3)指定默認值爲CLR Type默認值後,在添加數據時,若顯式指定了CLR Type默認值,那麼此時將會在數據庫自動生成(由上添加數據生成的SQL沒有Status列可知)。

(4)若日期指定默認值爲數據庫自動生成,但添加時顯式指定日期,此時將覆蓋數據庫自動生成的默認日期。

字符串映射探討

對於字符串默認映射類型爲NVARCHAR且長度爲MAX,同時爲可空,是否可空經過IsRequired來修正。若是映射爲VARCHAR類且長度爲50,咱們可經過HasColumnType方法來指定類型,以下:

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

在EF Core 2.1以前,咱們經過HasColumnType方法指定類型,而經過HasMaxLength指定長度遷移會拋異常,那麼如今是否能夠呢?,答案是:遷移不會拋異常,但結果長度不正確,以下:

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

 

在EF 6.x中對於char、nchar等須要經過HasMaxLength和IsFixedLength方法類修正實現,在EF Core中都統一經過HasColumnType方法實現便可。

總結 

本文比較詳細的介紹了主鍵映射和屬性映射以及映射默認值問題,下一節咱們詳細講解關係映射。三 個月沒寫博客主要是私下去錄製了ASP.NET Core MVC基礎進階視頻,涉及到一些細節和基本原理,感興趣的童鞋能夠了解下,若您有任何疑問可私信於我,連接地址:https://study.163.com/course/courseMain.htm?courseId=1005573012&share=2&shareId=400000000517056,後續也會發布到騰訊課堂。咱們下節再會,感謝您的耐心閱讀。

相關文章
相關標籤/搜索