簡體   English   中英

F#類型提供者與C#接口+實體框架

[英]F# type providers vs C# interfaces + Entity Framework

問題非常技術性,它深深地介於F#/ C#差異之間。 我很可能錯過了一些東西。 如果您發現概念錯誤,請發表評論,我將更新問題。

讓我們從C#世界開始吧。 假設我有一個簡單的業務對象,稱之為Person (但是,請記住,有100多個對象遠比我們使用的業務領域中的對象復雜得多):

public class Person : IPerson
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
}

我使用DI / IOC,所以我從來沒有真正傳遞過一個Person 相反,我總是使用一個接口(如上所述),稱之為IPerson

public interface IPerson
{
    int PersonId { get; set; }
    string Name { get; set; }
    string LastName { get; set; }
}

業務要求是可以從數據庫序列化/反序列化該人員。 假設我選擇使用Entity Framework,但實際的實現似乎與問題無關。 此時我可以選擇引入“數據庫”相關類,例如EFPerson

public class EFPerson : IPerson
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
}

以及相關的數據庫相關屬性和代碼,為簡潔起見,我將跳過,然后使用Reflection復制PersonEFPerson之間的IPerson接口的屬性,或者直接使用EFPerson (作為IPerson傳遞)或執行其他操作。 這是相當無關緊要的,因為消費者總是會看到IPerson ,因此可以隨時更改實施,而消費者對此一無所知。

如果我需要添加一個屬性,那么我會首先更新接口IPerson (假設我添加一個屬性DateTime DateOfBirth { get; set; } )然后編譯器將告訴我要修復什么。 但是,如果我從界面中刪除該屬性(假設我不再需要LastName ),那么編譯器將無法幫助我。 但是,我可以編寫一個基於反射的測試,它將確保IPersonPersonEFPerson等的屬性相同。 這不是真的需要,但它可以完成然后它將像魔術一樣工作(是的,我們確實有這樣的測試,他們確實像魔術一樣工作)。

現在,讓我們來到F#世界。 這里我們有類型提供程序,它完全消除了在代碼中創建數據庫對象的需要:它們由類型提供程序自動創建!

涼! 但是嗎?

首先,有人必須創建/更新數據庫對象,如果涉及多個開發人員,那么數據庫可能並將在不同分支中升級/降級是很自然的。 到目前為止,根據我的經驗,當涉及到F#類型的提供者時,這是一個極度痛苦的問題。 即使使用C#EF Code First來處理遷移,也需要一些“廣泛的薩滿舞蹈”來使F#類型的提供者“滿意”。

其次,默認情況下,F#世界中的所有內容都是不可變的(除非我們使其變為可變),因此我們顯然不希望向上游傳遞可變數據庫對象。 這意味着一旦我們從數據庫加載一個可變行,我們希望盡快將其轉換為“本機”F#不可變結構,以便僅使用上游的純函數。 畢竟,使用純函數可以減少所需測試的數量,我猜,5到50次,具體取決於域。

讓我們回到我們的Person 我現在將跳過任何可能的重新映射(例如數據庫整數到F#DU情況和類似的東西)。 所以,我們的F# Person看起來像那樣:

type Person =
    {
        personId : int
        name : string
        lastName : string
    }

因此,如果“明天”我需要將dateOfBirth : DateTime添加到此類型,那么編譯器將告訴我所有需要修復的地方。 這很好,因為C#編譯器不會告訴我在哪里需要添加出生日期,...除了數據庫。 F#編譯器不會告訴我我需要將數據庫列添加到表Person 但是,在C#中,由於我必須首先更新接口,編譯器會告訴我必須修復哪些對象,包括數據庫。

顯然,我希望F#中的兩個世界都是最好的。 雖然這可以通過接口實現,但它感覺不到F#方式。 畢竟,DI / IOC的模擬在F#中完全不同,它通常通過傳遞函數而不是接口來實現。

所以,這里有兩個問題。

  1. 如何在F#world中輕松管理數據庫上/下遷移? 而且,首先,當涉及到許多開發人員時,在F#世界中實際進行數據庫遷移的正確方法是什么?

  2. 如上所述,實現“最好的C#世界”的F#方式是什么:當我更新F#類型Person然后修復我需要添加/刪除屬性到記錄的所有地方時,最合適的F#方式是什么?在編譯時或至少在我沒有更新數據庫以匹配業務對象的測試時“失敗”?

如何在F#world中輕松管理數據庫上/下遷移? 而且,首先,當涉及到許多開發人員時,在F#世界中實際進行數據庫遷移的正確方法是什么?

管理Db遷移的最自然方式是使用db本機工具,即純SQL。 在我們的團隊中,我們使用dbup包,為每個解決方案我們創建一個小型控制台項目,以便在開發和部署期間匯總數據庫遷移。 消費者應用程序都在F#(類型提供程序)和C#(EF)中,有時使用相同的數據庫。 奇跡般有效。

你提到了EF Code First。 F#SQL提供程序本質上都是“Db First”,因為它們基於外部數據源(數據庫)生成類型,而不是相反。 我不認為混合兩種方法是個好主意。 事實上,我不會向任何人推薦EF Code First來管理遷移:普通的SQL更簡單,不需要“廣泛的薩滿舞”,更多的人可以更靈活和更容易理解。 如果您對手動SQL腳本感到不舒服,並考慮將EF Code First用於自動生成遷移腳本,那么即使MS SQL Server Management Studio設計器也可以為您生成遷移腳本

如上所述,實現“最好的C#世界”的F#方式是什么:當我更新F#類型Person然后修復我需要添加/刪除屬性到記錄的所有地方時,最合適的F#方式是什么?在編譯時或至少在我沒有更新數據庫以匹配業務對象的測試時“失敗”?

我的食譜如下:

  • 不要使用接口。 如你所說 :)

接口,它只是感覺不到F#方式

  • 不要讓類型提供程序中的自動生成類型泄漏到精簡db訪問層之外。 它們不是業務對象,並且EF實體都不是事實。
  • 而是將F#記錄和/或有區別的聯合聲明為您的域對象。 根據需要對它們進行建模,不要受db模式的限制。
  • 在db訪問層中,從自動生成的db類型映射到域F#類型。 Type Provider自動生成的每種類型的使用都在此處開始和結束。 是的,這意味着您必須手動編寫映射並在此處引入人為因素,例如,您可能會意外地將FirstName映射到LastName。 在實踐中,這是一個微小的開銷,並且解耦的好處超過它的幅度。
  • 如何確保你不要忘記映射一些屬性? 這是不可能的,如果記錄未完全初始化,F#編譯器將發出錯誤。
  • 如何添加新屬性而不是忘記初始化它? 從F#代碼開始:向域記錄/記錄添加新屬性,F#編譯器將引導您進入所有記錄實例(通常只有一個)並強制您使用某些內容對其進行初始化(您必須相應地添加遷移腳本/升級數據庫架構) )。
  • 如何刪除屬性,不要忘記清理數據庫模式的所有內容。 從另一端開始:從數據庫中刪除列。 類型提供程序類型和域F#記錄之間的所有映射都會破壞並突出顯示多余的屬性(更重要的是,它會強制您仔細檢查它們是否真的是多余的並重新考慮您的決定)。
  • 事實上,在某些情況下,您可能希望保留數據庫列(例如,用於歷史/審計目的),並僅從F#代碼中刪除屬性。 當域模型與數據庫模式分離時,它只是眾多場景中的一種(而且相當罕見)。

簡而言之

  • 通過純SQL遷移
  • 域類型是手動聲明的F#記錄
  • 從類型提供程序到F#域類型的手動映射

更短

堅持單一責任原則,享受好處。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM