F# type providers vs C# interfaces + Entity Framework

前端 未结 1 1669
梦如初夏
梦如初夏 2021-02-13 11:46

The question is very technical, and it sits deeply between F# / C# differences. It is quite likely that I might’ve missed something. If you find a conceptual error, please, comm

1条回答
  •  长情又很酷
    2021-02-13 12:03

    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?

    Most natural way to manage Db migrations is to use tools native to db i.e. plain SQL. At our team we use dbup package, for every solution we create a small console project to roll up db migrations in dev and during deployment. Consumer apps are both in F# (type providers) and C# (EF), sometimes with the same database. Works like a charm.

    You mentioned EF Code First. F# SQL providers are all inherently "Db First" because they generate types based on external data source (database) and not the other way around. I don't think that mixing two approaches is a good idea. In fact I wouldn't recommend EF Code First to anyone to manage migrations: plain SQL is simpler, doesn't require "extensive shaman dancing", infinitely more flexible and understood by far more people. If you are uncomfortable with manual SQL scripting and consider EF Code First just for automatic generation of migration script then even 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)?

    My recipe is as follows:

    • Don't use the interfaces. as you said :)

    interfaces, it just does not feel the F# way

    • Don't let autogenerated types from type provider to leak outside thin db access layer. They are not business objects, and neither EF entities are as a matter of fact.
    • Instead declare F# records and/or discriminated unions as your domain objects. Model them as you please and don't feel constrained by db schema.
    • In db access layer, map from autogenerated db types to your domain F# types. Every usage of types autogenerated by Type Provider begins and ends here. Yes, it means you have to write mappings manually and introduce human factor here e.g. you can accidentally map FirstName to LastName. In practice it's a tiny overhead and benefits of decoupling outweigh it by a magnitude.
    • How to make sure you don't forget to map some property? It's impossible, F# compiler will emit error if record not fully initialized.
    • How to add new property and not forget to initialize it? Start with F# code: add new property to domain record/records, F# compiler will guide you to all record instantiations (usually just one) and force you to initialize it with something (you will have to add a migration script / upgrade database schema accordingly).
    • How to remove a property and don't forget to clean up everything up to db schema. Start from the other end: delete column from database. All mappings between type provider types and domain F# records will break and highlight properties that became redundant (more importantly, it will force you to double check that they are really redundant and reconsider your decision).
    • In fact in some scenarios you may want to preserve database column (e.g. for historical/audit purposes) and only remove property from F# code. It's just one (and rather rare) of multitude of scenarios when it's convenient to have domain model decoupled from db schema.

    In Short

    • migrations via plain SQL
    • domain types are manually declared F# records
    • manual mapping from Type Providers to F# domain types

    Even Shorter

    Stick with Single Responsibility Principle and enjoy the benefits.

    0 讨论(0)
提交回复
热议问题