【ASP.NET Core】EF Core - “導航屬性”

来源:https://www.cnblogs.com/tcjiaan/archive/2018/11/01/9882099.html
-Advertisement-
Play Games

“導航屬性”是實體框架用得算是比較頻繁的概念。 首先,它是類型成員,其次,他是屬性,這不是 F 話,而是明確它的本質。那麼,什麼場景下會用到導航屬性呢?重點就落在“導航”一詞上了,當實體 A 需要引用實體 B 時,實體 A 中需要公開一個屬性,通過這個屬性,能找到關聯的實體 B。 又或者,X 實體表 ...


“導航屬性”是實體框架用得算是比較頻繁的概念。

首先,它是類型成員,其次,他是屬性,這不是 F 話,而是明確它的本質。那麼,什麼場景下會用到導航屬性呢?重點就落在“導航”一詞上了,當實體 A 需要引用實體 B 時,實體 A 中需要公開一個屬性,通過這個屬性,能找到關聯的實體 B。

又或者,X 實體表示你的博客,P 實體表示你發的一篇博文。你的博客肯定會發很多博文的,所以,X 實體中可能需要一個 List<P> 類型的屬性,這個屬性包含了你的博客所發表的文章。通過一個實體的屬性成員,可以定位到與之有關聯的實體,這就是導航的用途了。就像你開著車去穿越神農架一樣,迷路了就打開高德導航(前提是不存在定位干擾)。

現在跑江湖的人多,通過各種江湖騙術發家致富。有了不正常的財富積累後,他們開始大量買車,還買地打造個人車庫。於是,Person 實體代表這些有錢人,CarData 實體表示他們買的各種壕車。

    public class Person
    {
        public int PID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<CarData> Cars { get; set; }
    }

    public class CarData
    {
        public Guid CarID { get; set; }
        public string CarAttribute { get; set; }
        public decimal Cost { get; set; }
    }

每個 Person 都有 Cars 屬性,表示各自所購買的車。這個 Cars 就是導航屬性,通過這個屬性能找到關聯的 CarData 實體。

再定義一個數據上下文類。

    public class MyContext : DbContext
    {
        public DbSet<Person> Persons
        {
            get { return Set<Person>(); }
        }
    }

公開一個 Persons 屬性,便於訪問,當然了,你覺得我那樣寫代碼太多,你可以直接來這樣。

        public DbSet<Person> Persons { get; set; }

兩種寫法都是可以的。

這一次,我選擇用 SQLite 資料庫,新的 .net core 框架沒有包含訪問 SQLIte 的程式集,不過沒關係,有 Nuget 啥都能裹進來。怎麼安裝 nuget 包就不用我教了,你會的。最簡單不粗暴的方法就是直接在 nuget 控制臺中執行 install-package 命令。

PM> install-package microsoft.entityframeworkcore.sqlite

看到下麵這一堆東東就說明完成了。

 

回到 MyContext 類,進行一下連接字元串的配置。

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("data source=TMD.db");
        }

重寫 OnConfiguring 方法,再調用 UseSqlite 擴展方法,就可以設置連接字元串了。

還要重寫 OnModelCreating 方法,要做兩件事情:一是為每個實體設置主鍵;二是為兩個實體建立關係。

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 設置主鍵
            modelBuilder.Entity<Person>().HasKey(p => p.PID);
            modelBuilder.Entity<CarData>().HasKey(c => c.CarID);
            // 映射實體關係,一對多
            modelBuilder.Entity<Person>().HasMany(p => p.Cars);
        }

在本例中,你懂的,一個人可以有 N 輛車,因此 Person 與 CarData 之間是“一對多”的關,故而實體 Person 可以 HasMany 個 CarData 對象,其中,Cars 即是導航屬性。

 

註意:由於 MyContext 類重寫了 OnConfiguring 方法,所以,在 MyContext 類的構造函數中,無需接收 DbContextOptions<MyContext> 的依賴註入 ,在 Startup.ConfigureServices 方法中也無需再調用 UseSqlite 方法,你只需 Add 一下就可以了。

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

 在 Main 入口點中,先創建 host 實例。

            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseEnvironment(EnvironmentName.Development)
                .UseUrls("http://localhost:7552")
                .UseStartup<Startup>()
                .Build();

此時,不要急著調用 Run 方法。因為咱們還沒創建資料庫呢。當然,你可以用老周上一篇中介紹的方法,在 nuget 控制臺中,用 Add-Migration 命令添加遷移,然後用 Update-Database 命令創建資料庫。不過,本文中,老周將通過代碼在運行階段創建資料庫。

            using (IServiceScope scope = host.Services.CreateScope())
            {
                MyContext cxt = scope.ServiceProvider.GetRequiredService<MyContext>();
                if (cxt.Database.EnsureCreated())
                {
                    // 插入一些記錄
                    Person p1 = new Person
                    {
                        Name = "王老三",
                        Age = 65,
                        Cars = new List<CarData>
                        {
                            new CarData
                            {
                                CarAttribute= "黃色蘭博基尼",
                                Cost = 1500020002.00M
                            },
                            new CarData
                            {
                                CarAttribute = "景泰藍 吉利瑞博GE",
                                Cost = 138_000M
                            }
                        }
                    };
                    cxt.Persons.Add(p1);
                    Person p2 = new Person
                    {
                        Name = "朱大日",
                        Age = 72,
                        Cars = new List<CarData>
                        {
                            new CarData
                            {
                                CarAttribute = "瑪瑙紅 別克VELITE 5",
                                Cost = 289_500M
                            },
                            new CarData
                            {
                                CarAttribute = "雅韻金 本田INSPIRE",
                                Cost = 171000M
                            },
                           new CarData
                           {
                               CarAttribute = "奧迪A4L",
                               Cost = 401000M
                           }
                        }
                    };
                    cxt.Persons.Add(p2);
                    // 更新到資料庫
                    cxt.SaveChanges();
                }
            }

IServiceScope 是個有趣的玩意兒,它創建一個基於當前作用域的服務列表,從該對象中獲取的服務實例,其生命周期只在當前 scope 中有效。這特別適用於臨時實例化服務的情景。比如這裡,MyContext 只是暫時實例化,等創建資料庫並寫入測試數據後,就可以 Dispose 了。

初始化資料庫後,可以運行 host 了。 

        host.Run();

 

添加一個控制器,為了簡單,咱們不創建 View 了,就直接返回 JSON 數據好了,就當 Web API 來使用。

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

        [HttpGet]
        public ActionResult ListData()
        {
            return Json(context.Persons);
        }
    }

 

現在可以運行了,用諸如 Postman 等測試工具,請求 <root url>/test/listdata,結果發現驚人一幕。

[
    {
        "pid": 1,
        "name": "王老三",
        "age": 65,
        "cars": null
    },
    {
        "pid": 2,
        "name": "朱大日",
        "age": 72,
        "cars": null
    }
]

我相信,很多人都遇到了這個問題,所以,本文老周也順便解釋一下這個問題。如你所見,cars 屬性是 null,明明是添加了 CarData 對象的,為啥會 null,你是不是開始懷疑人生了?千萬不要輕易懷疑人生,那樣是很不負責任的。

好,不賣關子了。出現這個問題,是因為導航屬性的狀態在預設情況下不會自動去還原的,不然的話,會增加對象引用,所以預設是不載入的。那麼,你會問,那麼 CarData 實體的數據記錄到底載入了沒?載入了的,你可以寫一個 Action 去試試的。

        [HttpGet]
        public ActionResult CarList()
        {
            var cars = context.Set<CarData>().ToList();
            return Json(cars);
        }

然後,你訪問一下 <root url>/test/carlist,看看下麵的結果。

[
    {
        "carID": "36e97ed0-56b1-4d92-bb2d-aeec9f9e1b43",
        "carAttribute": "黃色蘭博基尼",
        "cost": 1500020002
    },
    {
        "carID": "0fd6c2a0-d4ef-4838-bc08-43a5cb024eef",
        "carAttribute": "景泰藍 吉利瑞博GE",
        "cost": 138000
    },
    {
        "carID": "c9eb20c8-931e-4563-b380-cbee926015c8",
        "carAttribute": "瑪瑙紅 別克VELITE 5",
        "cost": 289500
    },
    {
        "carID": "3d563693-5ae0-4682-bd53-c7fc87e951de",
        "carAttribute": "雅韻金 本田INSPIRE",
        "cost": 171000
    },
    {
        "carID": "2294a556-fd02-49c3-b4b2-559f15413e75",
        "carAttribute": "奧迪A4L",
        "cost": 401000
    }
]

我沒騙你吧,有數據的呀。

現在我們有這個需求,要求還原導航屬性的狀態,那咋辦呢?再次回到 ListData 方法,把它改成這樣。

        [HttpGet]
        public ActionResult ListData()
        {
            var persons = context.Persons.Include(p => p.Cars).ToList();
            return Json(persons);
        }

調用 Include 方法記得引入 Microsoft.EntityFrameworkCore 命名空間,這個不用我多說了。Incluse 擴展方法的意思就是載入導航屬性中的內容,它會自動還原狀態,知道哪些 CarData 實例與 Person 實例有關。

再次運行,請求一下 <root url>/test/listdata,這下你就放心了。

[
    {
        "pid": 1,
        "name": "王老三",
        "age": 65,
        "cars": [
            {
                "carID": "36e97ed0-56b1-4d92-bb2d-aeec9f9e1b43",
                "carAttribute": "黃色蘭博基尼",
                "cost": 1500020002
            },
            {
                "carID": "0fd6c2a0-d4ef-4838-bc08-43a5cb024eef",
                "carAttribute": "景泰藍 吉利瑞博GE",
                "cost": 138000
            }
        ]
    },
    {
        "pid": 2,
        "name": "朱大日",
        "age": 72,
        "cars": [
            {
                "carID": "c9eb20c8-931e-4563-b380-cbee926015c8",
                "carAttribute": "瑪瑙紅 別克VELITE 5",
                "cost": 289500
            },
            {
                "carID": "3d563693-5ae0-4682-bd53-c7fc87e951de",
                "carAttribute": "雅韻金 本田INSPIRE",
                "cost": 171000
            },
            {
                "carID": "2294a556-fd02-49c3-b4b2-559f15413e75",
                "carAttribute": "奧迪A4L",
                "cost": 401000
            }
        ]
    }
]

怎麼樣,滿意了吧。

 

接下來,咱們再看看反過來的情況,咋返過來呢?我們假設以汽車為主,現在是每輛車都對應著一位車主信息,每個人只與一輛車關聯,所以,車與人之間是“一對一”的關係。

先定義實體類,結構與前面的差不多。

    public class Person
    {
        public int PID { get; set; }
        public string Name { get; set; }
    }

    public class CarData
    {
        public int CarID { get; set; }
        public string CarAttribute { get; set; }
        public decimal Cost { get; set; }
        public Person Owner { get; set; }
    }

這一次,如你所見,導航屬性是 CarData 類的 Owner 屬性,即該車的車主信息,引用一個 Person 實例。

下麵定義 DbContext。

    public class MyContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public DbSet<CarData> Cars { get; set; }
    }

重寫 OnModelCreating 方法。

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Person>().HasKey(p => p.PID);
            modelBuilder.Entity<CarData>().HasKey(c => c.CarID);

            modelBuilder.Entity<CarData>().HasOne(c => c.Owner);
        }

除了每兩個實體設置主鍵外,請註意看最後一行,這一次,CarData 實體只對應著一個 Person 實例,所以是 HasOne,導航屬性是 Owner。

重寫 OnConfiguring 方法,配置連接字元串,這一次就用 SQL Server LocalDB,輕量級的。

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

Main 方法中的做法與前面一樣,初始化 WebHost 後,先創建資料庫,填一下垃圾數據,然後再啟動 host。

            var host = new WebHostBuilder()
                .UseKestrel()
                .UseEnvironment("debug")
                .UseUrls("http://localhost:19230")
                .UseStartup<Startup>()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .Build();

            using(IServiceScope scope = host.Services.CreateScope())
            {
                MyContext cxt = scope.ServiceProvider.GetRequiredService<MyContext>();
                if (cxt.Database.EnsureCreated())
                {
                    Person p1 = new Person
                    {
                        Name = "王阿基"
                    };
                    Person p2 = new Person
                    {
                        Name = "劉二打"
                    };
                    Person p3 = new Person
                    {
                        Name = "李無牙"
                    };
                    CarData c1 = new CarData
                    {
                        CarAttribute = "三無產品 A款",
                        Cost = 150000M,
                        Owner = p1
                    };
                    CarData c2 = new CarData
                    {
                        CarAttribute = "三無產品 F款",
                        Cost = 67500M,
                        Owner = p2
                    };
                    CarData c3 = new CarData
                    {
                        CarAttribute = "三無產品 2018款",
                        Cost = 76000M,
                        Owner = p3
                    };
                    cxt.Persons.Add(p1);
                    cxt.Persons.Add(p2);
                    cxt.Persons.Add(p3);
                    cxt.Cars.Add(c1);
                    cxt.Cars.Add(c2);
                    cxt.Cars.Add(c3);
                    cxt.SaveChanges();
                }
            }

            host.Run();

接下來,創建一個控制器。

    public class SampleController : Controller
    {
    }  

通過依賴註入,獲得 MyContext 實例。

        readonly MyContext context;
        public SampleController(MyContext cxt)
        {
            context = cxt;
        }

定義一個獲取數據列表的 Action。

        [HttpGet]
        public ActionResult List()
        {
            var cars = context.Cars.Include(c => c.Owner);
            return Json(cars.ToList());
        }

Include 方法的調用與前面一樣,只是註意這次是以 CarData 實體為主,順便載入導航屬性 Owner 的內容。

Postman 測試結果。

[
    {
        "carID": 1,
        "carAttribute": "三無產品 A款",
        "cost": 150000,
        "owner": {
            "pid": 1,
            "name": "王阿基"
        }
    },
    {
        "carID": 2,
        "carAttribute": "三無產品 F款",
        "cost": 67500,
        "owner": {
            "pid": 2,
            "name": "劉二打"
        }
    },
    {
        "carID": 3,
        "carAttribute": "三無產品 2018款",
        "cost": 76000,
        "owner": {
            "pid": 3,
            "name": "李無牙"
        }
    }
]

同樣,這也達到預期的效果了。

 

我們查看一下所生成的資料庫,你會發現,Cars 表中生成了一列,名為 OwnerPID,引用的是關聯的 Person 實例的主鍵。載入數據時,就是通過這一列來還原兩個實體之間的關係的。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • protected void btnEdit_Click(object sender, EventArgs e) { if (txtCarPlateNumber.Text.Trim() == "") { ClientScript.RegisterStartupScript(GetType(), ..... ...
  • List<T> 當T為值類型的時候 去重比較簡單,當T為引用類型時,一般根據業務需要,根據T的中幾個屬性來確定是否重覆,從而去重。 查看System.Linq下的Enumerable存在一個去重方法 通過實現IEqualityComparer<T>比較器來實現對象的比較。 IEqualityComp ...
  • 在進行地圖開發過程中,一般能接觸到以下三種類型的地圖坐標系: 1.WGS-84原始坐標系,一般用國際GPS紀錄儀記錄下來的經緯度,通過GPS定位拿到的原始經緯度,Google和高德地圖定位的的經緯度(國外)都是基於WGS-84坐標系的;但是在國內是不允許直接用WGS84坐標系標註的,必須經過加密後才 ...
  • 托盤圖標設置 新建一個NotifyIcon,會在托盤處顯示一個圖標。 NotifyIcon.Icon可以直接設置一個ico圖片,也可以延用原有程式的圖標。 notifyIcon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application. ...
  • 作者:依樂祝 原文地址:https://www.cnblogs.com/yilezhu/p/9891346.html 因為之前一直沒怎麼玩過CentOS,大多數時間都是使用Win10進行開發,然後程式都部署在Window Server2008或者Window Server2012上!因此想嘗試下Li ...
  • 由於枚舉的基礎類型類型為基本的數值類型,支持位運算,因此可以使用一個值表示多個枚舉的組合,在定義枚舉時需要指定枚舉數為2的冪指數方便進行位運算,即枚舉數為1,2,4,8…,或1,1<<1,1<<2…: 此時可以使用位移運算方便的獲取多個枚舉的組合變數: 或在組合中刪除其中一項枚舉: 需要判斷是否包含 ...
  • TestDto實體 完整代碼詳情請移步我的github:https://github.com/gordongaogithub/GetCountFromList ...
  • 在登陸按鈕驗證成功之後可以將會話結果改為OK 項目代碼里設置先顯示登陸視窗,之後驗證會話結果之後進行顯示主窗體 program.cs ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...