1. 程式人生 > >【ASP.NET Core】EF Core - “影子屬性”

【ASP.NET Core】EF Core - “影子屬性”

有朋友說老週近來部落格更新較慢,確實有些慢,因為有些 bug 要研究,另外就是老周把部分內容轉到直播上面,所以寫部落格的內容減少了一點。

老周覺得,視訊直播可能會好一些,雖然我的水平一般,不過直播時,老周可以現場演示,可能會比看部落格效果要好(因為現場演示,有時候會有失誤,沒辦法,水平有限)。還有一個,就是.NET 的資料其實很多,畢竟也發展了十幾年了,有些東西如果別人都寫過了,那我也不好意思重複了。.NET Core 儘管是跨平臺版本,但核心依然是.net 基礎,我們不需要全新去學習,只要掌握一些新的變化就可以了。目前比較期待 .NET Core 3 的正式釋出,等正式上線了,老周再挑一些有意義的內容寫一下。

此外,老周也可能會寫一寫其他方面的部落格,比如 Python、GO、Ruby、Typescript 等。老周並不是只會玩.NET ,只不過老周是主攻 .NET,在接觸 .NET 之前,老周就學過很多東西,比如古老的 QBasic、Pascal ,老周在上初中時就學過。後來向 VB、C、C++ 進攻,順便把 Ruby、Python、PB 也調戲一下,後來有一段時間,Delphi 和 E 語言也挺流行的,所以順便也玩了兩把。

再後來,學過 Java 和 PHP,拋 Java 而投 .NET 是因為 Java 太複雜,效率不高,沒有深度把玩的興趣。現在所謂的 Python 很熱門純屬是商業炒作,Python 又不是什麼新玩意兒,很古老了,當然相對於 C 來說,是新了一點,究其特點,就是一種指令碼語言(雖然有人死要說它不是指令碼語言)。現在網上更有些無知小輩,以為自己會寫幾行 Python 程式碼就到處去蹭熱點,告訴你,老周當年學各種程式語言時,說不定你還沒出生呢。所以,如果你真心喜歡 Python 的話,你用心去學就是了(其實老周也喜歡用 Python 來做圖表),不必理會商業炒作。

記得去年 C 語言也被商業炒作了幾個月,再往前幾年,Javascript 和 Web 前端也被拼命炒作,說得好像 js 是萬能的似的,嚇得老周都不敢寫前端了。最近幾年,IT 界開始懷舊了,各種遠古生物都被挖出來了,可能是現在計算機行業已經沒什麼可以創新的原因吧。現在說得較多的是人工智障,這個可以用,也可以不用,反正不痛不癢,算不上生產力革命(至少其震動效果比不上當年 Office 問世時對企業生產的影響大)。不過,人工智障在某些輔助領域還是有用的,比如現在有些小區的智慧門,應用效果還可以。但是,漏洞也是百出的,總之,人類可以用它來進行輔助,但不能過於依賴它。它不能解決所有問題。

許多科幻小說都會說人類會被機器人消滅。機器人也是人創造出來的,機器不可能比人強,也不可能滅掉人類(除非機器人比奧特曼裡面的超獸還牛逼)。如果人類真的智力在衰減,那麼根源還是在人類自己。說白了就是,只有可能是人類自己滅掉自己。你也不用覺得太恐怖,其實只要你不要太依賴機器就好,不要失去你的本能和思考方式就行。

就像我們碼農,老周也一樣,天天跟計算機打交道,但老週一直堅持:用電腦,但不依賴電腦,多做些機器不能做的事。再加一句:科學只能解決數學和工程問題,而人的問題,需要哲學和美學來解決

=============================================================================

好了,以上的都是 F 話,下面咱們聊正題。今天咱們耍一下 EF Core 中的影子屬性。這個詞翻譯版本 TMD 多,有翻譯為“卷影屬性”的,現在的文件又改為“陰影屬性”,這不好聽,太有心理陰影了,故而,老周覺得,叫“影子屬性”好一些。

不管叫什麼,你只要知道它是個啥就行。老周喜歡一句話總結,所以,來一句話:

模型類中沒有定義的,但資料表中存在的屬性——即模型類與資料表中沒有對應關係的屬性。

老周就用一個簡單的示例來說明一下吧。這個示例也是老周在視訊直播時用的。

假設,有個模型類,叫 Student。

    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

但我想要一個屬性,用來記錄資料記錄被寫進資料庫的時間,即還有一個屬性,叫 InsertTime,不過,這個屬性在 Student 類中是沒有定義的,但在資料表中是有這一欄位的。

因此,在從 DbContext 類派生時,需要重寫 OnModelCreating 方法,通過 Model Builder 來定義這個“隱藏”的屬性。

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Property<DateTime>("InsertTime");
        }

這個 InsertTime 屬性就成了影子屬性了。

 

下面是 DbContext 派生類的完整程式碼,我放出來是方便你去抄襲的,放心吧,無版權稅的,儘管抄。

    public class MyContext : DbContext
    {
        public DbSet<Student> Students { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"server=(localdb)\MSSQLLocalDB;database=TestDb");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Property<DateTime>("InsertTime");
        }
    }

 

現在,到 Startup 類中,註冊一下服務,讓自定義的資料庫上下文可以進行依賴注入。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<MyContext>();
        }

剛剛在 MyContext 類中已經配置連線字串了,所以註冊服務時就不用指定連線字串了。

 

按老周前面所寫的博文,接下來是建立資料遷移,不過,這一次老周使用的是直接在執行的時候建立資料庫,方法也是很簡單的,找到 Main 入口點,把裡面的程式碼改一下(專案模板預設生成的程式碼不進行大改)。

        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();
            using(IServiceScope scope= host.Services.CreateScope())
            {
                MyContext c = scope.ServiceProvider.GetService<MyContext>();
                c.Database.EnsureCreated();
            }
            host.Run();
        }

也就是在 host 執行之前,建立一個“作用域”級別的服務例項,建立資料庫,這個例項是臨時使用,不遵循服務容器的生命週期規則。EnsureCreated 方法會檢測資料庫是否存在,如果不存在,就建立,然後返回 true;如果資料庫已經存在,不做任何處理並返回 false。

如果你不打算寫入一些初始資料,可以不在乎方法的返回值,如果要寫入初始資料,可以 if 一下,如果方法返回 true,就寫一下資料進去(true 表示新資料庫)。

 

接下來,咱們用一個 API 控制器來測試一下。

    [Route("api/[action]")]
    public class TestController : Controller
    {
        readonly  MyContext context;
        public TestController(MyContext c) => context = c;

        [HttpPost]
        public ActionResult AddNew([FromBody]Student stu)
        {
            ……
        }

        [HttpGet]
        public JArray GetList()
        {
            ……
        }
    }

資料庫上下文的例項因為已經註冊到服務容器中,所以通過建構函式可以得到其例項引用。這個控制器有兩個 action,AddNew 方法用來提交資料,以 POST 方式訪問。

        [HttpPost]
        public ActionResult AddNew([FromBody]Student stu)
        {
            // 新增實體
            context.Students.Add(stu);
            // 設定影子屬性
            context.Entry(stu).Property<DateTime>("InsertTime").CurrentValue = DateTime.Now;
            context.SaveChanges();
            return Ok("操作成功");  
        }

記得,引數要加上 FromBody 特性,因為它要從 HTTP 訊息正文中提取,上次我直播時就是忘了寫這個,所以提交不到資料。

這裡要注意影子屬性的賦值方法,因為它沒有在 Student 類中公開,你不能通過訪問成員來設定它,只能先通過 CurrentValue 屬性來設定。

 

然後還有一個 GetList 方法,以 GET 方式來訪問。有來返回所有 Student 資料。此處我用 JArray 以 JSON 陣列格式返回。

        [HttpGet]
        public JArray GetList()
        {
            var q = from s in context.Students
                    select new
                    {
                        s.Id,
                        s.Name,
                        s.Age,
                        // 讀取影子屬性值
                        InsertTime = EF.Property<DateTime>(s, "InsertTime")
                    };
            JArray arr = new JArray();
            foreach (var s in q)
            {
                JObject obj = new JObject();
                obj.Add("id", new JValue(s.Id));
                obj.Add("name", new JValue(s.Name));
                obj.Add("age", new JValue(s.Age));
                obj.Add("add_time", new JValue(s.InsertTime));
                arr.Add(obj);
            }
            return arr;
        }

讀取影子屬性的方法是用 EF.Property 靜態方法,第一個引數是實體模型類的例項,第二個引數是影子屬性的名字。這個方法只能在 LINQ 樹中使用,如果不在 LINQ 中用會發生異常。這裡還有一個問題,就是在 select 子句中用 new 返回的匿名型別無法建立 JObject 物件,所以只好手動去構建。

 

現在可以測一下了,首先提交一下資料。

{
    "name":"李三跳",
    "age":52
}

接著獲取一下資料列表。

[
    {
        "id": 1,
        "name": "什麼鬼",
        "age": 29,
        "add_time": "2018-11-26T11:15:38.2012809"
    },
    {
        "id": 2,
        "name": "陳大扣",
        "age": 83,
        "add_time": "2018-11-26T11:21:07.5374308"
    },
    {
        "id": 3,
        "name": "李三跳",
        "age": 52,
        "add_time": "2018-11-26T12:41:51.758849"
    }
]

 

好了,示例就完成了。

影子屬性的典型用法就像剛剛這個例子這樣,可以用來記錄資料的新增時間或者更新時間,但這種資料,一般不需要在實體模型中公開。