關於有預設值的欄位在用EF做插入操作時的思考(續)

weixin_33858249發表於2017-11-02

問題描述

今天下午(看現在這時間,應該是昨天下午了哈),園友 choon 寫了這樣一篇博文《關於有預設值的欄位在用EF做插入操作時的思考》。

博文內容主要記錄的是 choon 使用 EF 做資料插入與更新時,欄位預設值的問題,這個問題我們平常應該都會遇到,但是,最後博文內容包括評論,並沒人能給出一個準確的答案,真是很可惜(知識點的博文都是一侃一大堆,而這些實際專案遇到的問題卻回答不上來,又有什麼用呢,哎。。。)。詳細內容請檢視上面的博文,這邊我再簡單敘述下:

資料庫有一個 Users 表,表中有一個 CreateDate 欄位,我們希望使用 EF 的時候,插入 User 資料,不需要插入 CreateDate 的值,而是通過預設值生成。

  1. CreateDate 欄位為 null:使用 EF 的 ADD 操作(沒有給 User 物件賦予 CreateDate 的值),插入的結果是 CreateDate 值為 null。
  2. CreateDate 欄位不為 null:還是按照上面的操作,插入 User 報錯。

choon 最後給出的解決方式是:

<Property Name="CreateDate" Type="datetime" Nullable="false" StoreGeneratedPattern="Computed" />

後來,choon 又補充這樣實現的兩個問題(看過下面的內容,你就知道為什麼會出現這兩個問題了):

  1. 如果將 StoreGeneratedPattern 值設定為 Identity,只要一修改 CreateDate 欄位就會拋異常;
  2. 如果把 StoreGeneratedPattern 值設定為 Computed 不會拋異常,但值仍然沒有被修改,即使你寫了 user.CreateDate = "xxx"。

問題分析

為了方面理解,我按照當時實現的步驟敘述下,因為我喜歡 EF 的 CodeFirst 模式,所以這邊我就用它來做演示,看一下示例程式碼:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace CodeFirstDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new ProductContext())
            {
                var product = new Product { Name = "xishuai" };
                db.Products.Add(product);
                db.SaveChanges();

                Console.WriteLine("success");
                Console.ReadKey();
            }
        }
    }

    public class Product
    {
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime? CreateTime { get; set; }
    }

    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
    }
}

這是我們一般的實現方式,注意這段程式碼:var product = new Product { Name = "xishuai" }; 我並沒有給 CreateTime 進行賦值,為了可以使資料庫生成成功,我還把 CreateTime 設定為 null(DateTime?),但是執行的結果是:資料庫生成了,卻只有 ID 和 Name 欄位,而且新增資料失敗:

異常資訊:“由於表 'Products' 中不存在列 'CreateTime',ALTER TABLE ALTER COLUMN 失敗。”,什麼原因呢?主要是沒有指定 CreateTime 屬性(Attribute),比如[Required],但是我們發現 Name 也沒有指定啊,為什麼它卻可以生成資料庫列?因為我們在 Add 的時候指定 Name 的值了,這樣 EF 會自動識別這些欄位進行生成列。

我們可以先把資料庫生成一下,然後再進行實驗,可以暫時把新增資料程式碼改為:var product = new Product { Name = "xishuai", CreateTime = DateTime.Now };,這樣資料庫就可以成功生成了,之後再還原一下,我們按照 choon 的配置,做下面類似的操作:

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public DateTime? CreateTime { get; set; }

上面新增的屬性配置和 choon 在 config 中的配置是一樣的,只不過這個場景是 CodeFirst 下。毫無疑問,這樣新增資料像 choon 一樣,還是會無效果或抱異常(沒有賦值 CreateTime),那究竟是什麼問題呢?我們再看一下 StoreGeneratedPattern 的列舉值(MSDN詳情):

  • None:一個值,指示它不是伺服器生成的屬性。這是預設值。如果沒有StoreGeneratedPattern屬性,其值就預設為None.
  • Identity:執行插入時生成一個值,但在執行更新時保持不變。
  • Computed:執行插入和更新時都將生成一個值。

Identity 是什麼意思?其實是標識鍵的意思,也就是我們常說的自增鍵,如果我們把上面示例中 CreateTime 的資料型別改為 int,配置還是原來的配置,但是資料識可以新增的,園中也有人做了一個示例,詳情請訪問:Entity Framework 資料生成選項 DatabaseGenerated

在 MSDN 中也又相應的示例說明,我貼一下關於這一點的總結(MSDN詳情):

You read above that by default, a key property that is an integer will become an identity key in the database. That would be the same as setting DatabaseGenerated to DatabaseGenerationOption.Identity. If you do not want it to be an identity key, you can set the value to DatabaseGenerationOption.None.

英語不太好,請自行理解,說了這麼多,難道沒有解決方式嗎?當然會有,只是你比較懶而已,google 搜尋:“code first datetime default” 、“databasegenerated datetime” 或 “An error occurred while updating the entries. See the inner exception for details” 關鍵字,你就會發現答案。

解決方案

首先,Product 中的 CreateTime 屬性,還是之前的配置:

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public DateTime? CreateTime { get; set; }

我們要做的是使用 CodeFirst 遷移(怎麼遷移?請檢視:初試Code First(附Demo)),先輸入命令“Enable-Migrations”啟動遷移,然後再“Add-Migration Update1”新增遷移,這時候會生成 Update1 遷移檔案,開啟後進行如下更改:

namespace CodeFirstDemo.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class Update1 : DbMigration
    {
        public override void Up()
        {
            //AlterColumn("dbo.Products", "CreateTime", c => c.DateTime());
            AlterColumn("dbo.Products", "CreateTime", c => c.DateTime(defaultValueSql: "GETDATE()"));
        }
        
        public override void Down()
        {
            AlterColumn("dbo.Products", "CreateTime", c => c.DateTime());
        }
    }
}

程式碼什麼意思,我就不多說了,改完之後,輸入命令“Update-Database”更新到資料庫,然後我們再進行測試:

可以看到資料是新增成功的,不放心的話可以去資料庫瞧瞧,有人可能會有疑問,我們增加了 DateTime 值的預設 SQL,是不是就不需要對 DateTime 進行 DatabaseGenerated 配置了呢?這個我試過,去掉 DatabaseGenerated 是會報錯的,至於為什麼?其實你看到這,應該會明白上面 choon 所提出的兩個問題,這邊我說一下我的理解,但是不一定正確哦:

  1. 如果將 StoreGeneratedPattern 值設定為 Identity,只要一修改 CreateDate 欄位就會拋異常:StoreGeneratedPattern 設定為 Identity,也就是標識鍵,但資料型別不是 int,而是 dateTime,最重要的是在增加或修改資料的時候,EF 找不到"the formula for the computed column"(計算列的公式-來自MSDN),所以不報錯才怪。
  2. 如果把 StoreGeneratedPattern 值設定為 Computed 不會拋異常,但值仍然沒有被修改,即使你寫了 user.CreateDate = "xxx":將 StoreGeneratedPattern 設定為 Computed(執行插入和更新時都將生成一個值),既然是生成,你再進行 Set 也是沒用的,而且像上面一樣,找不到此列的計算公式(可以理解為 GETDATE),所以沒用任何值新增或修改。

這是 EF CodeFirst 中的解決方式,至於“Model First”生成 edmx 中的方式解決,這個我還沒試(肯定是修改 Config 配置檔案),但我覺得都大同小異。還有就是,大家專案中使用 EF,如果可以的話,建議使用 CodeFirst 模式,不是一般的爽哦。。。

示例程式碼下載:http://pan.baidu.com/s/1pJG3jab




本文轉自田園裡的蟋蟀部落格園部落格,原文連結:http://www.cnblogs.com/xishuai/p/entity-framework-code-first-computed-getdate.html,如需轉載請自行聯絡原作者

相關文章