Vogen Help

Tips for working with EFCore

Identifying types that are generated by Vogen

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: 16 September 2024