c# - F# 类型提供程序 vs C# 接口(interface) + Entity Framework

标签 c# entity-framework f# type-providers

这个问题非常技术性,它深深地存在于 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大约。相反,我总是使用一个接口(interface)(上面提到的),称之为 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; }
}

以及相关的数据库相关属性和代码,为了简洁我将跳过,然后使用反射复制IPerson的属性。 Person之间的接口(interface)和 EFPerson或者只使用 EFPerson (传递为 IPerson )直接或做其他事情。这是无关紧要的,因为消费者总是会看到 IPerson因此可以随时更改实现,而消费者对此一无所知。

如果我需要添加一个属性,那么我会更新接口(interface) IPerson首先(假设我添加了一个属性 DateTime DateOfBirth { get; set; } )然后编译器会告诉我要修复什么。但是,如果我从接口(interface)中删除该属性(假设我不再需要 LastName ),那么编译器将不会帮助我。但是,我可以编写一个基于反射的测试,这将确保 IPerson 的属性。 , Person , EFPerson等是相同的。这不是真正需要的,但它可以完成,然后它会像魔术一样工作(是的,我们确实有这样的测试,它们确实像魔术一样工作)。

现在,让我们进入 F# 世界。这里我们有类型提供者,它完全消除了在代码中创建数据库对象的需要:它们是由类型提供者自动创建的!

凉爽的!但是是吗?

首先,必须有人创建/更新数据库对象,如果涉及多个开发人员,那么很自然地,数据库可能并且将会在不同的分支中升级/降级。到目前为止,根据我的经验,当涉及 F# 类型提供程序时,这是一个非常痛苦的问题。即使使用 C# EF Code First 来处理迁移,也需要一些“广泛的萨满舞”来让 F# 类型提供者“高兴”。

其次,默认情况下,F# 世界中的一切都是不可变的(除非我们将其设为可变),因此我们显然不想向上游传递可变数据库对象。这意味着一旦我们从数据库加载了一个可变行,我们希望尽快将其转换为“ native ”F# 不可变结构,以便仅与上游的纯函数一起使用。毕竟,使用纯函数会减少所需的测试次数,我猜是 5 到 50 倍,具体取决于域。

让我们回到我们的 Person .我现在将跳过任何可能的重新映射(例如,将数据库整数转换为 F# DU 案例和类似的东西)。所以,我们的 F# Person看起来像这样:
type Person =
    {
        personId : int
        name : string
        lastName : string
    }

所以,如果“明天”我需要添加 dateOfBirth : DateTime到这种类型,然后编译器会告诉我所有需要修复的地方。这很棒,因为 C# 编译器不会告诉我需要在哪里添加出生日期,……除了数据库。 F# 编译器不会告诉我我需要去往表中添加一个数据库列 Person .但是,在 C# 中,由于我必须先更新接口(interface),编译器会告诉我必须修复哪些对象,包括数据库对象。

显然,我想要在 F# 中两全其美。虽然这可以使用接口(interface)来实现,但它只是感觉不到 F# 的方式。毕竟,DI/IOC 的模拟在 F# 中是非常不同的,它通常是通过传递函数而不是接口(interface)来实现的。

所以,这里有两个问题。
  • 如何在 F# 世界中轻松管理数据库向上/向下迁移?而且,首先,当涉及许多开发人员时,在 F# 世界中实际进行数据库迁移的正确方法是什么?
  • 如上所述,实现“C# 最佳世界”的 F# 方法是什么:当我更新 F# 时,输入 Person然后修复我需要向记录添加/删除属性的所有地方,在编译时或至少在测试时当我没有更新数据库以匹配业务时,最合适的 F# 方式是“失败”对象?
  • 最佳答案

    How can I easily manage database up / down migrations in F# world? And, to start from, what is the proper way to actually do the database migrations in F# world when many developers are involved?



    管理数据库迁移最自然的方法是使用数据库原生的工具,即普通 SQL。在我们的团队中,我们使用 dbup包,对于每个解决方案,我们都会创建一个小型控制台项目,以在开发和部署期间汇总数据库迁移。消费者应用程序同时使用 F#(类型提供程序)和 C# (EF),有时使用相同的数据库。奇迹般有效。

    你提到了EF Code First。 F# SQL 提供程序本质上都是“数据库优先”,因为它们基于外部数据源(数据库)生成类型,而不是相反。我不认为混合两种方法是一个好主意。事实上,我不会向任何人推荐 EF Code First 来管理迁移:纯 SQL 更简单,不需要“广泛的萨满舞”,更加灵活并且被更多人理解。
    如果您对手动 SQL 脚本不满意并考虑 EF Code First 仅用于自动生成迁移脚本,那么甚至 MS SQL Server Management Studio designer can generate migration scripts for you

    What is the F# way to achieve “the best of C# world” as described above: when I update F# type Person and then fix all places where I need to add / remove properties to the record, what would be the most appropriate F# way to “fail” either at compile time or at least at test time when I have not updated the database to match the business object(s)?



    我的食谱如下:
  • 不要使用接口(interface)。正如你所说:)

  • interfaces, it just does not feel the F# way


  • 不要让类型提供程序的自动生成类型泄漏到瘦数据库访问层之外。它们不是业务对象,并且 neither EF entities are事实上。
  • 而是将 F# 记录和/或有区别的联合声明为您的域对象。随心所欲地对它们进行建模,并且不受 db 模式的限制。
  • 在数据库访问层中,从自动生成的数据库类型映射到您的域 F# 类型。 类型提供程序自动生成的类型的每次使用都从这里开始和结束。 是的,这意味着您必须手动编写映射并在此处引入人为因素,例如您可能会不小心将名字映射到姓氏。在实践中,这是一个很小的开销,解耦的好处远远超过它。
  • 如何确保您不会忘记映射某些属性?这是不可能的,如果记录未完全初始化,F# 编译器将发出错误。
  • 如何添加新属性而不忘记初始化它?从 F# 代码开始:向域记录/记录添加新属性,F# 编译器将引导您进行所有记录实例化(通常只有一个)并强制您使用某些内容对其进行初始化(您必须相应地添加迁移脚本/升级数据库架构) )。
  • 如何删除一个属性,不要忘记清理所有数据库架构。从另一端开始:从数据库中删除列。类型提供程序类型和域 F# 记录之间的所有映射都将中断并突出显示变得多余的属性(更重要的是,它将迫使您仔细检查它们是否真的多余并重新考虑您的决定)。
  • 事实上,在某些情况下,您可能希望保留数据库列(例如用于历史/审计目的)并且仅从 F# 代码中删除属性。当域模型与 db 模式分离很方便时,这只是众多场景中的一种(而且相当罕见)。

  • 总之
  • 通过普通 SQL 迁移
  • 域类型是手动声明的 F# 记录
  • 从类型提供程序到 F# 域类型的手动映射

  • 更短

    坚持单一职责原则并享受好处。

    关于c# - F# 类型提供程序 vs C# 接口(interface) + Entity Framework ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54282322/

    相关文章:

    c# - 如何将样式写入 WPF DataGridColumnHeader

    c# - 使用紧凑的 Framework 3.5 样式化 ListView 控件

    c# - 从 'System.String' 到 'System.TimeSpan' EF 4.1/MySql Connector/Net 6.4.4 的转换无效

    c# - 是否可以在 MS Entity Framework 中查询新添加的对象

    f# - 使用 OcamlYacc/FsYacc 表示可选语法和重复

    c# - 排序的列表项

    c# - 如何禁用 Visual Studio 2015 中的所有空格自动格式化?

    c# - LINQ to Entities 无法识别该方法,并且无法将此方法转换为存储表达式

    dictionary - 使用具有不可比较对象的 Map

    f# - 返回匿名记录而不是元组的注意事项