Fluentmigrator: Make TypeMap injectable, with fluent TypeMap interface

Created on 24 Sep 2019  路  6Comments  路  Source: fluentmigrator/fluentmigrator

Is there any way to add or override a specific TypeMap? I would like to make a one-off mapping to map a specific DbType to a specific Template without having to recreate the entire TypeMap class.

For example, SQLite does not support 'DateTimeOffset', but EFCore automatically serializes DateTimeOffset to TEXT within SQLite. When creating my Migrations, I know that I can use IfDatabase to test for SQLite and another to test if not SQLite, creating the appropriate column type for each database engine, but this is a lot of repeated code for each of my DateTimeOffset columns.

Instead, it would be much easier to update the SQLiteTypeMap to somehow fire off SetTypeMap(DbType.DateTimeOffset, "TEXT");. This would eliminate all of the IfDatabase calls. However, with a lot of internal classes and instantiation within constructors, this seems difficult to do without duplicating most of FluentMigrator.Runner.SQLite.

What am I missing? I can't find any documentation for a one-off TypeMap override.

question question-answered

All 6 comments

What am I missing? I can't find any documentation for a one-off TypeMap override.

In my opinion, it is not a good idea to change the meta-model for how FluentMigrator works across systems. "EFCore automatically serializes DateTimeOffset to TEXT within SQLite" isn't sufficient enough justification to break the meta-model.

That said, FluentMigrator 4.0 is customizing a lot more stuff, and it is conceivable this could be on the table to make extensible. However, breaking a common Meta-Model pretty much defeats the purpose of using FluentMigrator to abstract away syntactic differences among SQL providers. We try to avoid abstracting away semantic differences. If SQLite doesn't support it, we'd rather throw an error and have you fix it by providing a custom mapping.

tl;dr: use AsCustom extension method to specify an arbitrary data type.
https://fluentmigrator.github.io/api/v3.x/FluentMigrator.Builders.IColumnTypeSyntax-1.html#FluentMigrator_Builders_IColumnTypeSyntax_1_AsCustom_System_String_

@jayharris I'll close this - please open a new ticket if you think this should be a feature. I've given you some context around how we think about it, but I'm always open to being wrong about a point of view/perspective. True innovation is when customers say, "It doesn't have to be that way."

@jayharris See also: https://github.com/fluentmigrator/fluentmigrator/issues/1032 - if you would like to help with this, that would be the first steps to considering making the meta model not just documented, but fungible by consumers.

I do think it would be worthwhile to be extensible. Even if EFCore didn't automatically support serialization of DateTimeOffset, it would be helpful to say "Hey, I know that this database doesn't natively support this data type, but I've created a custom mapping in my own codebase to handle it. So, whenever a migration is created for this data type, create the column using this other type, instead."

As you put it, the entire point of the Type Map system is abstracting away different data column type mappings. I don't need everyone on the dev team knowing that SQLite doesn't support DateTimeOffset and remembering to create their mappings using IfDatabase tests to store it as a string. Instead, our codebase can extend one new mapping condition to abstract that away and all AsDateTimeOffset calls automatically handle the conversion.

I will add a feature request.

Fair, thank you.

@jayharris I've been thinking about this issue the last few days.

  1. I think the general way we specify database and database-processor specific configurations is rather verbose and does not lend itself well to the common meta model mentioned in #1032
  2. Are you familiar with how AutoFixture works, and perhaps event CsvHelper ClassMap works?

Here is how AutoFixture works:

  1. a Fixture object instance stores rules for how to build objects of a given type. Those are called specimens.
  2. You can then Customize each Specimen by using Do and Without.

```c#
fixture.Customize(x => x
.Do(p => p.Badge = fixture.Create())
.Do(p => p.CreatedByUser = fixture.Create())
.Do(p => p.ModifiedByUser = fixture.Create())
.Without(p => p.Badge)
.Without(p => p.CreatedByUser)
.Without(p => p.ModifiedByUser)
);


The `Do .. x .. Without .. y` convention is basically a verbose, procedural embedding of "Replace .. y .. With .. x"

Similarly, CsvHelper lets you define a ClassMap for how to map rows in a CSV to POCOs, using the following syntax:

```c#
    public sealed class PersonRecordMap : ClassMap<PersonRecord>
    {
        public PersonRecordMap()
        {
            AutoMap(); // common meta model based on PersonRecord's member data types
            Map(m => m.Badge).Name("Badge ID"); // The header of the CSV file is different than the property name
            Map(m => m.DepartmentID).Ignore(); // The CSV file doesn't have a department ID.
            Map(m => m.IsBadRecord).Ignore(); // Properties specific to the POCO used for stateful row processing.
            Map(m => m.ErrorMessages).Ignore(); // Properties specific to the POCO used for stateful row processing.
        }
    }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

cpistiner picture cpistiner  路  9Comments

biju-ps picture biju-ps  路  9Comments

itw2016 picture itw2016  路  11Comments

ondravondra picture ondravondra  路  8Comments

fredrik-lundin picture fredrik-lundin  路  7Comments