Migration from v9 to v10 - PawelGerr/Thinktecture.Runtime.Extensions GitHub Wiki
This guide covers breaking changes and new features when migrating from v9 to v10.
- Dropped support for .NET 7 - Minimum version is now .NET 8.0
- Dropped support for Entity Framework Core 7 - Minimum version is now EF Core 8.0
- Minimum .NET SDK increased to 8.0.416
All types and members previously marked with [Obsolete] have been deleted. This includes:
Entity Framework Core:
-
UseValueObjectValueConverter→ UseUseThinktectureValueConvertersinstead -
AddValueObjectConverters→ UseAddThinktectureValueConvertersinstead
Interfaces:
-
IEnum<TKey>→ UseISmartEnum<TKey>instead -
IEnum<TKey, T, TValidationError>→ UseISmartEnum<TKey, T, TValidationError>instead -
IValueObjectFactory<TValue>→ UseIObjectFactory<TValue>instead -
IValueObjectFactory<T, TValue, TValidationError>→ UseIObjectFactory<T, TValue, TValidationError>instead -
IComplexValueObjectinterface deleted (implementation was already generated automatically)
Enums:
-
ValueObjectAccessModifierenum → UseAccessModifierenum instead
ASP.NET Core:
-
TrimmingSmartEnumModelBinderclass deleted
Attributes:
- Several obsolete attribute constructor overloads removed
- Updated Swashbuckle.AspNetCore.SwaggerGen to 10.0.1 - If you use the Swashbuckle integration package, ensure compatibility with Swashbuckle 10.x
-
SmartEnumAttribute<TKey>.KeyMemberTypeis read-only - The setter has been removed. Assigning the property never had any effect, because the key type always comes from the type argumentTKey. Removing the assignment from your code does not change any behavior, but code that still assigns the property no longer compiles. -
TxIsNullableReferenceTypegetters report every non-value type - The gettersT1IsNullableReferenceTypetoT5IsNullableReferenceTypeofAdHocUnionAttributeand of the genericUnionAttribute<T1, T2, ...>family now returntruefor a stateless member of any non-value type, for example an interface. Previously they returnedtrueonly for classes. This affects the reflection surface of the attribute instance; the generated code is unchanged, because the source generator determines reference types from the compilation model.
v10 adds the analyzer rules TTRESG079 (Error), TTRESG108 (Warning) and TTRESG109 (Warning), and it
widens TTRESG078 from ReadOnlySpan<char> factories to object factories with any ref-struct value
type. Each rule flags a configuration that already failed at build time or at runtime, or was silently ignored before,
so your code behaves as it did. Still, an existing project can report new build diagnostics right
after the upgrade. See Analyzer Diagnostics for the full list.
v10 introduces a new, more structured configuration API for Entity Framework Core integration. The old callback-based approach is deprecated but still supported for backward compatibility.
The UseThinktectureValueConverters method now accepts a Configuration object that provides:
- Type-safe configuration for Smart Enums and Keyed Value Objects
- Built-in max length strategies for automatic database column sizing
- Better defaults with smart enum max length calculation out-of-the-box
.UseThinktectureValueConverters(configureEnumsAndKeyedValueObjects: property =>
{
if (property.ClrType == typeof(ProductType))
{
var maxLength = ProductType.Items.Max(i => i.Key.Length);
property.SetMaxLength(maxLength + (10 - maxLength % 10)); // round up to next 10
}
else if (property.ClrType == typeof(ProductName))
{
property.SetMaxLength(200);
}
})Option 1: Use default configuration (automatic max length for string-based smart enums)
.UseThinktectureValueConverters()
// or explicitly:
.UseThinktectureValueConverters(Configuration.Default)Option 2: Custom configuration with strategies
.UseThinktectureValueConverters(new Configuration
{
SmartEnums = new SmartEnumConfiguration
{
// Automatically calculates max length from items and rounds to next 10
MaxLengthStrategy = DefaultSmartEnumMaxLengthStrategy.Instance
},
KeyedValueObjects = new KeyedValueObjectConfiguration
{
MaxLengthStrategy = new CustomKeyedValueObjectMaxLengthStrategy((type, keyType) =>
{
if (type == typeof(ProductName))
return 200;
return MaxLengthChange.None; // Max length stays unchanged
})
}
})The following changes do not break compilation, but they change what your code does at runtime.
-
TryCreateno longer promises a non-null result whenNullInFactoryMethodsYieldsNull = true- With this setting,TryCreate(null, out var obj)returnstrueand leavesobjasnull. The generatedoutparameter therefore no longer carries[NotNullWhen(true)]. The runtime behavior is unchanged; only the nullability contract now describes it correctly. As a result, the compiler can report newCS8602warnings where you dereference the result after a successful call. Add a null check to fix them. See Null Value Handling. -
ParseandTryParsereject empty input whenEmptyStringInFactoryMethodsYieldsNull = true- For a string-keyed reference Value Object with this setting, the generatedParsenow throws aFormatExceptionfor empty or whitespace-only input. It previously returnednulleven though the return type is non-nullable.TryParsenow returnsfalsefor such input instead of returningtruewith anullresult. Because ASP.NET Core binds parameters throughIParsable<T>andISpanParsable<T>(minimal APIs, for example), empty input that used to bind tonullnow fails to bind. The MVC model binder of the ASP.NET Core integration package, the JSON and MessagePack integrations, and the factory methodsCreate,TryCreateandValidateare unchanged: empty input still yieldsnullthere. See Empty String Handling.
-
Set comparison methods reject
null- The sets returned byEmpty.Set<T>()andSingleItem.Set<T>(item)now throw anArgumentNullExceptionwith the parameter nameotherwhen you passnulltoIsSubsetOf,IsSupersetOf,IsProperSubsetOf,IsProperSupersetOf,OverlapsorSetEquals. Previously some of these methods returnedtrueorfalsefor anullargument, and others threw with the parameter namesource. The new behavior matchesHashSet<T>. -
Dictionary members reject
nullkeys - The dictionaries returned byEmpty.Dictionary<TKey, TValue>()andSingleItem.Dictionary(key, value)now throw anArgumentNullExceptionwith the parameter namekeywhen you pass anullkey to the indexer,ContainsKeyorTryGetValue.SingleItem.Dictionaryalso rejects anullkey at creation time. PreviouslyContainsKeyandTryGetValuereturnedfalseand the indexer threw aKeyNotFoundException. The new behavior matchesDictionary<TKey, TValue>.
-
Span-based JSON deserialization rejects non-string tokens (.NET 9+) - On .NET 9 and later, the
span-based
System.Text.Jsonconverter now throws aJsonExceptionfor any token that is neither a string nor a property name. Numbers and booleans were silently transcoded from their raw bytes and accepted before. This converter is used by string-based Smart Enums by default and by types with an[ObjectFactory<ReadOnlySpan<char>>(UseForSerialization = SerializationFrameworks.SystemTextJson)]. On .NET 8 such input already threw. -
MessagePack serializes items of derived Smart Enum types - Serializing an item through the
runtime type of a derived (nested) Smart Enum class no longer throws an
InvalidCastException. The resolver now wraps the formatter of the base type in a casting formatter for derived item types. A[MessagePackFormatter]attribute applied directly to the derived class is still honored. -
Object factories with a ref-struct value type are ignored instead of failing - MessagePack and
Newtonsoft.Json now ignore an object factory whose value type is a ref struct, and
System.Text.Json as well as the Swashbuckle schema filter ignore every ref-struct factory except
ReadOnlySpan<char>. These frameworks fall back to the key-based conversion instead of failing during type initialization. The analyzer reports TTRESG108 for such a configuration.
-
Primitive collections of string-based Smart Enums receive the max length on the element - The
max length strategy now configures the element type of a primitive collection instead of the
collection property itself. Previously the collection column got the length of a single item, for
example
nvarchar(10)for the whole collection. When you upgrade from v9 or from an earlier 10.x release, EF Core scaffolds a new migration for the affected columns. -
Max length strategies skip types with an Entity Framework object factory - The built-in max
length strategies no longer apply to a type that has an
[ObjectFactory<T>(UseWithEntityFramework = true)], because the factory decides the persisted value and that value may be longer than the key. Columns that were limited automatically before are now unlimited, so expect a new EF migration. Configure the length explicitly withHasMaxLengthif you need the limit. See Configuration and Max Length Strategies.
-
The
AllOfSmart Enum schema no longer emits a per-itemtitle- TheAllOfvariant now combines all items into a singleenumsubschema, because a conjunction of per-itemconstsubschemas can never be satisfied. SetSmartEnumSchemaExtension = SmartEnumSchemaExtension.VarNamesFromStringRepresentation(orVarNamesFromDotnetIdentifiers) to expose the item names through thex-enum-varnamesextension instead. See Available Options.
v10 adds the NestedUnionParameterNames option for configuring the Switch and Map parameter
names of nested Regular Unions. This is not a breaking change: the default behavior is unchanged.
By default the parameter names still include the intermediate type names to avoid conflicts.
Default (unchanged):
failure.Switch(
failureNotFound: notFound => ...,
failureUnauthorized: unauthorized => ...
);Set NestedUnionParameterNames = NestedUnionParameterNameGeneration.Simple on the union to opt into
the shorter names that omit the intermediate type names:
[Union(NestedUnionParameterNames = NestedUnionParameterNameGeneration.Simple)]
failure.Switch(
notFound: notFound => ...,
unauthorized: unauthorized => ...
);See Configuring nested union parameter names for details.