Core Concepts > Configuration

Enums

Enums are fully supported in dbExpression. Enums are challenging in the context of executing SQL statements, as enums are defined within an application domain and have to be "translated" to their numeric or string equivalent for persistence in the target database. To use enums with their constant value when authoring queries with dbExpression, scaffolding configuration is required.

Scaffolding configuration is required to use the constant value of an enum when authoring queries in dbExpression.

Scaffolding Configuration

dbExpression itself doesn't (and can't) understand something like MySuperDuperEnum that is defined in your application domain. You must tell dbExpression a little about your enums:

  • The fully qualified type name of your enum
  • Where you plan to use them

To scaffold enums, the dbexpression.config.json configuration file must list the fully qualified type names of each enum. Given the enums in the sample console application, we'll list those in the enums array of the configuration file:

{
...
"enums": [
        "SimpleConsole.Data.AddressType",
        "SimpleConsole.Data.GenderType",
        "SimpleConsole.Data.ProductCategoryType",
        "SimpleConsole.Data.PaymentMethodType",
        "SimpleConsole.Data.PaymentSourceType",
        "SimpleConsole.Data.AccessAuditResult"
    ]
...
}

The scaffolding process needs to know where you would like to use the enum types instead of the inferred default data type.

For example, if the AddressType column in the Address table has a DbType of int and allows null, the scaffolded property AddressType in the Address entity would have a CLR type of int?. To change this property type from the CLR type int? to AddressType?, an override must be provided in the dbexpression.config.json configuration file:

{
    ...
    {
        "apply": {
            "clrType": "SimpleConsole.Data.AddressType?",
            "to": {
                "path": "dbo.Address.AddressType"
            }
        }
    }
    ...
}

See the Reference section on Object Overrides for a full explanation of override properties and values.

This configuration indicates that for the column dbo.Address.AddressType the scaffolded property type will be the CLR type AddressType? for the property AddressType on the Address entity.

This is all you need to do if the Enum is stored as it's numeric value.

Runtime Configuration

Runtime configuration is required to persist enum values as strings, or to provide your own persistence strategy.

Persisting Enum Values as Strings

To persist enum values as strings, use the PersistAsString() method in runtime configuration to specify a value converter that will convert enum values from their numeric type to their name:

dbExpression.Configure(
    dbex => {

        dbex.AddDatabase<SimpleConsoleDb>(
            database => {
                ...
                database.Conversions.ForTypes(c => c
                        .ForEnumType<AddressType>().PersistAsString()
                        .ForEnumType<PaymentMethodType>().PersistAsString()
                );
                ...
            }
        );
    }
);

See the Reference section on Value Converters for a full explanation of configuring value converters.

This runtime configuration specifies that all uses of the AddressType and PaymentMethodType CLR types are persisted using the name/string value of the enum. Configuration using PersistAsString() will manage both the non-nullable and nullable versions of provided enum values.

Using this also assumes your target database column has a data type (varchar, etc) that can accept and persist the string version of the enum.

Configuration to persist an enum type as a string is global. dbExpression does not support persisting an enum type as a string in one table and it's numeric value in a different table.

Providing a Delegate for Persisting Enum Values

You have full control of the conversion of enum values as they are written to and read from the target database. Given the example above for an AddressType eum, the following configuration (purely for example) will ensure every Address type with an AddressType value of Mailing is persisted with a leading underscore and correctly converted when read from the target database:

dbExpression.Configure(
    dbex => {

        dbex.AddDatabase<SimpleConsoleDb>(
            database => {
                ...
                database.Conversions.ForTypes(c => c
                        .ForEnumType<AddressType>().Use(
                        to => {
                            if (to == AddressType.Mailing)
                                return $"_{AddressType.Mailing}";
                            return to;
                        },
                        from => {
                            var addressType = from as string;
                            if (string.IsNullOrWhiteSpace(addressType))
                                return null;
                            if (addressType == $"_{AddressType.Mailing}")
                                return AddressType.Mailing;
                            return (AddressType?)Enum.Parse(typeof(AddressType), addressType, true);
                        }
                    )
                );
                ...
            }
        );
    }
);

When configuring enums (and other primitive types) using delegates, delegates must manage null, regardless of the nullability of the target property on an entity.

But why? It's possible to logically create a null value for a field even though the property is defined as non-nullable. Think of a LEFT JOIN that returns a null value for a column that's marked NOT NULL. Let's say the AddressType property on the Address entity does not allow null values. With (default) scaffolding, a null AddressType can't be mapped to an Address entity. It's possible (and perfectly viable) to have a null value for AddressType when queries are structured to do just that. In the following example, a Person is not required to have an address, so returning a Person with no Address will result in a null for the AddressType field:

db.SelectOne(
        dbo.Person.FirstName,
        dbo.Person.LastName,
        dbo.Address.AddressType
    )
    .From(dbo.Person)
    .LeftJoin(dbo.PersonAddress).On(dbo.Person.Id == dbo.PersonAddress.PersonId)
    .LeftJoin(dbo.Address).On(dbo.PersonAddress.AddressId == dbo.Address.Id)
    .Execute();

In the dynamic result from execution of this query, the AddressType property may be a null value - and again this is perfectly viable based on the query. As conversions for AddressType use the overriden delegate to convert values, it must handle the null value to avoid a runtime exception.

Previous
Runtime

© 2024 dbExpression. All rights reserved.