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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

EF Core中避免贫血模型的三种行之有效的方法(翻译)

發布時間:2023/12/4 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EF Core中避免贫血模型的三种行之有效的方法(翻译) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

[Paul Hiles: 3 ways to avoid an anemic domain model in EF Core :https://www.devtrends.co.uk/blog/3-ways-to-avoid-an-anemic-domain-model-in-ef-core]

1.引言

在使用ORM中(比如Entity?Framework)貧血領域模型十分常見?。本篇文章將先探討貧血模型的問題,再去探究在EF?Core中使用Code?First時如何使用簡單的方法來避免貧血模型。

2.什么是貧血模型

在對領域建模后,輸出一系列類中僅包含一些簡單屬性聲明而不包含業務邏輯的模型,就屬于貧血模型。當使用Entity?Framework時,它們不僅僅是簡單的數據持有者而且包含有一堆public?getter和public?setters:

  • public?class?BlogPost

  • {

  • ????public?int?Id?{?get;?set;?}

  • ????[Required]

  • ????[StringLength(250)]

  • ????public?string?Title?{?get;?set;?}

  • ????[Required]

  • ????[StringLength(500)]

  • ????public?string?Summary?{?get;?set;?}

  • ????[Required]

  • ????public?string?Body?{?get;?set;?}

  • ????public?DateTime?DateAdded?{?get;?set;?}

  • ????public?DateTime??DatePublished?{?get;?set;?}

  • ????public?BlogPostStatus?Status?{?get;?set;?}

  • ????...

  • }

  • 由于其完全缺乏面向對象編程的原則,因此貧血模型通常被描述為反模式。他們需要調用者來完善驗證和其他業務邏輯。由于缺乏相應的抽象,就會導致代碼重復、較差的數據完整性,以及增加高層模塊的復雜性。 貧血模型是十分常見的。從我的經驗來看,EF中超過80%的領域模型都是貧血模型。這并不奇怪。幾乎所有的文檔和其他博客文章都以最簡單的方式展示了EF。他們專注于盡可能快地開始工作,而不是主張最佳實踐。

    3.改造為更豐富的領域模型(充血模型)

    下面我們將討論三種簡單的方式去豐富你的貧血模型。這幾種方法都非常簡單,僅需要最小的改動。

    3.1.移除無參公共構造函數

    除非你指定一個構造函數,否則你的類將有一個默認的無參數構造函數。這意味著你可以用下面的方式實例化你的類:

  • var blogPost = new BlogPost();

  • 在大多數情況下,這是沒有意義的。領域對象通常至少需要一些數據才能使其有效。創建沒有任何數據(如標題或URL)的BlogPost實例是沒有意義的,因為其僅僅是一個實例化對象,但對象卻不包含狀態和行為,不滿足數據有效性。有些人不同意,但是DDD社區普遍認為確保領域對象始終有效是有意義的。為了解決這個問題,我們可以像處理其他OO類一樣對待我們的域類,并引入一個參數化的構造函數:

  • public BlogPost(string title, string summary, string body)

  • {

  • ? ?if (string.IsNullOrWhiteSpace(title))

  • ? ?{

  • ? ? ? ?throw new ArgumentException("Title is required");

  • ? ?}

  • ? ?...

  • ? ?Title = title;

  • ? ?Summary = summary;

  • ? ?Body = body;

  • ? ?DateAdded = DateTime.UtcNow;

  • }

  • 現在在調用代碼必須提供最少的數據來滿足約束(構造函數)。這一變化提供了兩個積極成果:

  • 任何新實例化的BlogPost對象現在都保證有效。作用于BlogPost的任何代碼都無需檢查其有效性。領域對象在實例化時自動校驗自身的有效性。

  • 任何調用代碼都知道實例化對象所需的內容。使用無參數的構造函數,很容易構造對象,但卻不知道必須要構建的數據才能保證數據有效性。

  • 但不幸的是,在進行此更改后,您將發現在從數據庫中檢索實體時,您的EF代碼不再有效:

    InvalidOperationException:在實體類型'BlogPost'上找不到無參數的構造函數。為了創建'BlogPost'的實例,EF需要聲明一個無參數的構造函數。

    EF需要一個無參數的構造函數來查詢該做什么?幸運的是,盡管EF確實需要無參數構造函數,但它并不要求構造函數必須為public,所以我們可以為EF增加一個無參private構造函數,同時強制調用代碼使用參數化構造函數。擁有額外的構造函數顯然并不理想,但這些妥協通常可以時ORM與OO代碼更好地配合。

  • private BlogPost()

  • {

  • ? ?// just for EF

  • }

  • public BlogPost(string title, string summary, string body)

  • {

  • ? ?...

  • }

  • 3.2. 刪除公共屬性中的set方法

    上面介紹的參數化構造函數確保在實例化時對象處于有效狀態。盡管如此,這并沒有阻止您將屬性值更改為無效值。要解決這個問題,我們有兩個選擇:

  • 將驗證邏輯添加到屬性設置器

  • 防止直接修改屬性,改為使用與用戶操作相對應的方法

  • 向屬性設置器添加驗證是完全可以接受的,但意味著我們不能再使用自動屬性并且必須引入一個后臺字段。顯然這不是什么大問題:

  • private string title;

  • public string Title

  • {

  • ? ?get { return title; }

  • ? ?set

  • ? ?{

  • ? ? ? ?if (string.IsNullOrWhiteSpace(value))

  • ? ? ? ?{

  • ? ? ? ? ? ?throw new ArgumentException("Title must contain a value");

  • ? ? ? ?}

  • ? ? ? ?title = value;

  • ? ?}

  • }

  • 第二種方式更受歡迎的主要原因在于它更接近地模擬了現實世界中發生的事情。用戶不是孤立地更新單個屬性,而是傾向于執行一組已知操作(由UI或API接口確定)。這些操作可能會導致一個或多個屬性被更新,但通常情況下更多。業務邏輯依賴于上下文的場景是非常普遍的,這將會導致對屬性進行賦值的set中的驗證邏輯變得復雜而難以理解。作為基本示例,請考慮以下博客文章發布流程:

  • public void Publish()

  • {

  • ? ?if (Status == BlogPostStatus.Draft || Status == BlogPostStatus.Archived)

  • ? ?{

  • ? ? ? ?if (Status == BlogPostStatus.Draft)

  • ? ? ? ?{

  • ? ? ? ? ? ?DatePublished = DateTime.UtcNow;

  • ? ? ? ?}

  • ? ? ? ?Status = BlogPostStatus.Published;

  • ? ?}

  • }

  • 在這個例子中,我們有一個Publish()方法,它有一些簡單的邏輯和兩個可以更新的屬性。我們也可以將其作為一個屬性的setter來實現,但它不太清晰,尤其是從另一個類中調用它時:

  • blogPost.Status = BlogPostStatus.Published;

  • VS

  • blogPost.Publish();

  • 第一種方式的副作用是不能清晰的表達業務用例。

    當然,你在大多數代碼庫中看到的是根本不在領域對象中進行驗證。相反,這種類型的邏輯可以在下一層找到。這可能導致:

  • 更長的方法將領域特定的邏輯與編排、持久性和其他關注點混合在一起。

  • 不同動作之間重復的驗證邏輯。

  • 由于外部依賴性(需要使用Mock)而難以測試純領域邏輯。

  • 正如我們現在所期望的那樣,如果我們從每個屬性中徹底移除setter,EF將無法正常運行,但將訪問級別更改為private就可以很好地解決問題:

  • public class BlogPost

  • {

  • ? ?public int Id { get; private set; }

  • ? ?...

  • }

  • 這樣,所有屬性在類之外都是只讀的。為了允許更新我們的領域類,我們引入了相應類型動作的方法,如上面所示的Publish方法。

    通過刪除無參數構造函數和公共屬性設置器并添加動作類型的方法,我們現在擁有了始終有效的領域對象,并包含了與所討論的實體直接相關的所有業務邏輯,這是一個很大的改進。我們已經使我們的代碼同時更加健壯和簡單。

    雖然我們可以討論其他DDD概念,例如領域事件以及通過雙派遣模式([double-dispatch pattern:http://idior.cnblogs.com/articles/325036.html])使用領域服務,但它們的優勢,特別是簡單性方面的優勢遠不是那么明顯。 通常DDD概念中可以簡化代碼的是我們將在下面討論的值對象的使用。

    3.3.引入值對象

    [值對象:https://martinfowler.com/bliki/ValueObject.html]是不可變的(實例化后不允許更改)沒有身份標識的對象。值對象通常可以用來代替領域對象中的一個或多個屬性。

    值對象的經典示例包括貨幣,地址和坐標,但也可以使用值類型替換單個屬性,而不是使用字符串或整型。例如,不是將電話號碼存儲為字符串,而是可以創建一個帶有內置驗證的PhoneNumber值類型以及提取撥號代碼的方法等。

    下面的代碼顯示了一個實現為EF類使用的貨幣值對象:

  • public class Money

  • {

  • ? ?[StringLength(3)]

  • ? ?public string Currency { get; private set; }

  • ? ?public int Amount { get; private set; }

  • ? ?private Money()

  • ? ?{

  • ? ? ? ?// just for EF

  • ? ?}

  • ? ?public Money(string currency, int amount)

  • ? ?{

  • ? ? ? ?// todo validation

  • ? ? ? ?Currency = currency;

  • ? ? ? ?Amount = amount;

  • ? ?}

  • }

  • 貨幣和金額是內在聯系的。為了使數據有效,這兩條信息都是必需的。因此,對它們進行建模是有道理的。請注意,參數化的構造函數和私有屬性設置器的使用方式與我們在建模領域對象時所使用的完全相同。實體框架也需要一個私有無參數構造函數。

    在(RDBMS)數據持久性的上下文中,值類型不存在于單獨的數據庫表中。為了讓我們在實體框架中使用值對象,需要一個小的改動。這取決于您使用的EF版本。

    在EF6中,我們只需用[ComplexType]屬性修飾值對象:

  • [ComplexType]

  • public class Money

  • {

  • ? ?...

  • }

  • 在EF Core中,從版本2開始,我們可以使用Fluent API中不常用的OwnsOne方法:

  • public class BlogContext : DbContext

  • {

  • ? ?...

  • ? ?public DbSet<BlogPost> BlogPosts { get; set; }

  • ? ?protected override void OnModelCreating(ModelBuilder modelBuilder)

  • ? ?{

  • ? ? ? ?modelBuilder.Entity<BlogPost>().OwnsOne(x => x.AdvertisingFee);

  • ? ?}

  • }

  • 這里假定在我們的BlogPost實體上使用Money值對象,如下所示:

  • public class BlogPost

  • {

  • ? ?...

  • ? ?public Money AdvertisingFee { get; private set; }

  • ? ?...

  • }

  • 創建并運行遷移后,我們會發現我們的數據庫表現在包含兩個額外的列:

  • AdvertisingFee_Currency

  • AdvertisingFee_Amount

  • 使用值對象的好處與向富領域模型的轉變非常相似。豐富的領域模型不需要調用代碼來驗證領域模型,并提供了一個定義良好的抽象來進行編程。一個值對象進行自我驗證,因此包含值對象屬性的領域模型本身不需要知道如何驗證值類型。所有非常清晰和簡單。

    4. 溫馨提示

    當您打算從貧血域模型轉移到更豐富的領域模型時,您將立即體會到將領域級的業務邏輯封裝在領域對象中的好處。請注意,盡管如此,嘗試并不是件容易的事。在您的領域對象上創建一個方法來執行驗證,然后更新多個屬性無疑是件好事。但從領域對象發送電子郵件或保存到數據庫并不是您可能想要做的事情。重要的是要意識到,擁有豐富的領域模型并不否定另一層的需求來安排這些更高層次的關注。這是應用服務或命令處理程序的工作,具體取決于您的體系結構。

    5.關于單元測試的說明

    一個豐富的、自我驗證的領域模型的一個負面影響是它可以使測試變得更加困難。通過public setter,您可以簡單地將各個值分配給任何領域對象的屬性。這使您可以直接指定您需要的確切值,以便將對象置于特定狀態以進行測試。如果你鎖定你的屬性和構造函數,那么這種方法是不可能的。但這也不是一件壞事,它使單元測試變得稍微困難一點,但你所做的是確保你的測試是有效的。

    另一方面,它也使得測試領域對象本身的邏輯非常簡單。盡管你的應用服務/命令處理程序的單元測試幾乎肯定會需要一定程度的模擬,但你應該發現大部分領域對象測試的構建要簡單得多,并且通常不需要依賴模擬。

    6. 總結

    本文介紹了三種非常簡單的技術,您可以使用Entity Framework和EF Core從貧血域模型轉換為更為豐富的領域模型。使用參數化的構造函數可以確保我們的領域模型在實例化時有效。清除公共屬性setter確保我們的模型在其整個生命周期內保持有效狀態。在領域模型上內部執行驗證和引入更改狀態的方法使我們能夠集中業務邏輯并簡化調用代碼。最后,我們考察了值對象的使用,并解釋了他們如何進一步推進了這種簡化和邏輯封裝。


    總結

    以上是生活随笔為你收集整理的EF Core中避免贫血模型的三种行之有效的方法(翻译)的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: 欧洲亚洲一区 | 三级电影网址 | 最新欧美大片 | av在线不卡网站 | 性涩av| 岛国免费视频 | 国内爆初菊对白视频 | 中文字幕第28页 | 免费伊人 | 拍摄av现场失控高潮数次 | 国产黄色录像片 | 一个人看的www片免费高清中文 | 成人av播放 | 欧洲精品一区二区 | av在线麻豆 | 一个色的综合 | 在线一区av | 91精品国产综合久久久蜜臀粉嫩 | 在线观看av一区二区 | 日韩一区二区a片免费观看 伊人网综合在线 | 老牛影视av一区二区在线观看 | 青青草成人影视 | 国产又大又粗又爽的毛片 | 午夜精品久久久久久久久久久久久 | 成人黄色大片 | 久久色在线视频 | 精品一区二区在线观看 | 色乱码一区二区三区 | 中文字幕Av日韩精品 | 欧美一区二区三区久久综合 | 日韩少妇高潮抽搐 | 国产欧美视频一区二区三区 | 青青91| 精品爆乳一区二区三区 | 国产无遮挡aaa片爽爽 | 国产一区a | 2019毛片 | jizzjizz国产| 自拍第1页| 国产嫩草影视 | 91娇羞白丝 | 色丁香在线 | 成人精品视频在线播放 | 免费在线观看高清影视网站 | 最新一区二区三区 | 欧美精品免费播放 | 亚洲综合欧美日韩 | 久久午夜鲁丝片 | 久久婷婷综合国产 | 天堂成人av | 亚洲日本中文 | 欧美日韩一区不卡 | 五月激情片 | 日韩精品成人一区 | 日韩欧美精品中文字幕 | 亚洲区一区二 | 国产伦精品一区二区免费 | 黑人乱码一区二区三区av | 久草资源| 中文字幕亚洲精品在线观看 | 久久久成人精品一区二区三区 | 久久国产精品国语对白 | 激情五月综合 | 国产成人综合在线视频 | 欧美日韩第一区 | 亚洲综合免费观看高清完整版 | 亚洲一区二区三区四区在线 | 影视av | 黄色网址多少 | 天天摸天天做天天爽水多 | 美女扒开屁股让男人桶 | 国产日韩91| 亚洲av日韩精品久久久久久久 | 欧美精品123 | 成人午夜毛片 | 国产精品免费网站 | 黄色a一级 | 婷婷色基地 | 在线欧美国产 | 岛国av网址 | 日本韩国欧美一区二区 | 亚洲天堂首页 | 粉嫩av一区二区夜夜嗨 | 亚洲丁香花色 | 欧美激情视频二区 | 国产又粗又猛又爽又黄 | 欧美剧场 | 亚洲青草视频 | 国产欧美在线精品日韩 | 懂色av | 好吊操av | 快色av| 免费乱淫视频 | 精品一区二区成人免费视频 | 亚洲av成人精品毛片 | 成人一级毛片 | c逼| 亚洲精品久久久久久久久久久久久 | 粉嫩小箩莉奶水四溅在线观看 |