Vogen Help

Tips for working with EFCore

Is there a 'FromNullable' method?

No. Vogen is primarily designed for the domain layer where nulls just add confusion. For instance, what does a null SettlmentAmount mean? But sometimes, in other layers, where nulls do exist, it can be useful to have convenience methods, for instance, FromNullable(decimal?) or FromNullable(int?).

If you need FromNullable, then, in your assembly (normally in your 'Infrastructure' layer if you're using Onion/Clean Architecture), you can specify that Vogen create the IVogen<TWrapper, TPrimitive> on all types. Then, you can add extension members (C# 14+ only).

There is a sample provided showing how to do this.

Identifying types that are generated by Vogen

There are two ways to identify types that are generated by Vogen. You can add this in global config

[assembly: VogenDefaults( staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon)]

This generated an interface IVogen<TWrapper, TPrimitive>. This is useful for extension members.

This generates the interface on all value objects in the assembly. If you want to decide which types should be identified, then follow along...

Thank you to @jeffward01 for this item.

The goal of this is to identify types that are generated by Vogen.

Use Case

I use Vogen alongside EfCore, I like to programmatically add ValueConverters by convention, I need to identity which properties on my entities are generated value objects.

Solution

Vogen decorates the source it generates with the GeneratedCodeAttribute. This provides metadata about the tool which generated the code, this is what we'll use as an identifier.

Note: the code snippets use:

  • CSharpFunctionalExtensions - to use Maybe<T> BTW - if you're reading this, and have not checked out this library, I highly recommend. You don't need to adopt the pattern 100%, treat it as a buffet and take / use what you want

  • FluentAssertions—in the unit tests, just because

  • XUnit, because it's better than NUnit and MSTest 🙃

Code Snippet

// Helper class internal static class AttributeHelper { public static bool IsVogenValueObject(this Type targetType) { Maybe<GeneratedCodeAttribute> generatedCodeAttribute = targetType.GetClassAttribute<GeneratedCodeAttribute>(); return generatedCodeAttribute.HasValue && generatedCodeAttribute.Value.Tool == "Vogen"; } private static Maybe<TAttribute> GetClassAttribute<TAttribute>( this Type targetType) where TAttribute : Attribute { return targetType.GetAttribute<TAttribute>(); } }

Usage Example (From EfCore)

foreach (IMutableEntityType et in builder.Model.GetEntityTypes()) { PropertyInfo[] properties = et.ClrType.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType.IsVogenValueObject()) { // Huzzah! // Do something with the property that is a value // object generated by Vogen.... } } }

Testing

Data for unit test

[ValueObject<Guid>] // ReSharper disable once PartialTypeWithSinglePart public readonly partial struct VogenStronglyTypedId {} Unit Test ```c# public class VogenStronglyTypedIdTests { [Fact] public void ShouldIdentityVogenAttributeByHelperMethod() { Type vogenType = typeof(VogenStronglyTypedId); Maybe<GeneratedCodeAttribute> generatedCodeAttribute = vogenType.GetClassAttribute<GeneratedCodeAttribute>(); Assert.True(generatedCodeAttribute.HasValue); GeneratedCodeAttribute? valueOfAttribute = generatedCodeAttribute.Value; valueOfAttribute.Tool.Should().Be("Vogen"); } [Fact] public void ShouldIdentityVogenAttribute() { Type vogenType = typeof(VogenStronglyTypedId); vogenType.IsVogenValueObject() .Should() .Be(true); } }

Summary

Jeff says:

Adding all value objects to the ModelConfigurationBuilder

Thank you to CheloXL for this tip

Just to add another snippet that I'm using in my solutions: Add all VOs that have EfCoreValueConverter to the ModelConfigurationBuilder:

internal static class VogenExtensions { public static void ApplyVogenEfConvertersFromAssembly( this ModelConfigurationBuilder configurationBuilder, Assembly assembly) { var types = assembly.GetTypes(); foreach (var type in types) { if (IsVogenValueObject(type) && TryGetEfValueConverter( type, out var efCoreConverterType)) { configurationBuilder .Properties(type) .HaveConversion(efCoreConverterType); } } } private static bool TryGetEfValueConverter( Type type, [NotNullWhen(true)]out Type? efCoreConverterType) { var inner = type.GetNestedTypes(); foreach (var innerType in inner) { if (!typeof(ValueConverter).IsAssignableFrom(innerType) || !"EfCoreValueConverter".Equals( innerType.Name, StringComparison.Ordinal)) { continue; } efCoreConverterType = innerType; return true; } efCoreConverterType = null; return false; } private static bool IsVogenValueObject(MemberInfo targetType) { var generatedCodeAttribute = targetType.GetCustomAttribute<GeneratedCodeAttribute>(); return "Vogen".Equals( generatedCodeAttribute?.Tool, StringComparison.Ordinal); } }

Usage: In your dbcontext:

protected override void ConfigureConventions( ModelConfigurationBuilder configurationBuilder) { configurationBuilder.ApplyVogenEfConvertersFromAssembly( typeof(YOURDBCONTEXT).Assembly); }
Last modified: 27 March 2026