diff --git a/Directory.Build.props b/Directory.Build.props
index 0755370..c7047a8 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,7 +9,7 @@
Domain Primitives
ALTA Software llc.
Copyright © 2024 ALTA Software llc.
- 4.0.0
+ 4.1.0
diff --git a/EntityFrameworkCoreExample.md b/EntityFrameworkCoreExample.md
new file mode 100644
index 0000000..d0a93e8
--- /dev/null
+++ b/EntityFrameworkCoreExample.md
@@ -0,0 +1,126 @@
+
+# Generating EntityFrameworkCore Value Converters
+
+1. In the `.csproj` file where **AltaSoft.DomainPrimitives.Generator** is located, add the following item:
+
+```xml
+
+ true
+
+```
+
+**Note:** Ensure EntityFrameworkCore is added in the references.
+
+After this, ValueConverters for each DomainPrimitive will be generated.
+
+## Example
+Given a domain primitive `AsciiString`:
+
+```csharp
+///
+/// A domain primitive type representing an ASCII string.
+///
+///
+/// The AsciiString ensures that its value contains only ASCII characters.
+///
+public partial class AsciiString : IDomainValue
+{
+ ///
+ public static PrimitiveValidationResult Validate(string value)
+ {
+ var input = value.AsSpan();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (var i = 0; i < input.Length; i++)
+ {
+ if (!char.IsAscii(input[i]))
+ return "value contains non-ascii characters";
+ }
+
+ return PrimitiveValidationResult.Ok;
+ }
+}
+```
+
+The following converter will be generated:
+
+```csharp
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives.XmlDataTypes;
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using AltaSoft.DomainPrimitives;
+
+namespace AltaSoft.DomainPrimitives.XmlDataTypes.EntityFrameworkCore.Converters;
+
+///
+/// ValueConverter for
+///
+public sealed class AsciiStringValueConverter : ValueConverter
+{
+ ///
+ /// Constructor to create AsciiStringValueConverter
+ ///
+ public AsciiStringValueConverter() : base(v => v, v => v)
+ {
+ }
+}
+```
+
+**Note:** All Domain Primitives have implicit conversion to/from their primitive type. Therefore, no explicit conversion is required.
+
+## Helper Extension Method
+
+A helper extension method is also generated to add domain primitive conversions globally to `ModelConfigurationBuilder`:
+
+```csharp
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives.XmlDataTypes;
+using Microsoft.EntityFrameworkCore;
+using AltaSoft.DomainPrimitives.XmlDataTypes.EntityFrameworkCore.Converters;
+
+namespace AltaSoft.DomainPrimitives.XmlDataTypes.Converters.Extensions;
+
+///
+/// Helper class providing methods to configure EntityFrameworkCore ValueConverters for DomainPrimitive types of AltaSoft.DomainPrimitives.XmlDataTypes
+///
+public static class ModelConfigurationBuilderExt
+{
+ ///
+ /// Adds EntityFrameworkCore ValueConverters for specific custom types to ensure proper mapping to EFCore ORM.
+ ///
+ /// The ModelConfigurationBuilder instance to which converters are added.
+ public static ModelConfigurationBuilder AddDomainPrimitivePropertyConversions(this ModelConfigurationBuilder configurationBuilder)
+ {
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ configurationBuilder.Properties().HaveConversion();
+ return configurationBuilder;
+ }
+}
+```
diff --git a/README.md b/README.md
index 2c3888e..ccd89a1 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@ The **AltaSoft.DomainPrimitives.Generator** offers a diverse set of features:
* **NumberType Operations:** Automatically generates basic arithmetic and comparison operators, by implementing Static abstract interfaces. [More details regarding numeric types](#number-types-attribute)
* **IParsable Implementation:** Automatically generates parsing for non-string types.
* **XML Serialiaziton** Generates IXmlSerializable interface implementation, to serialize and deserialize from/to xml.
+* **EntityFrameworkCore ValueConverters** Facilitates seamless integration with EntityFrameworkCore by using ValueConverters to treat the primitive type as its underlying type. For more details, refer to [EntityFrameworkCore ValueConverters](EntityFrameworkCoreExample.md)
## Supported Underlying types
1. `string`
diff --git a/src/AltaSoft.DomainPrimitives.Generator/AltaSoft.DomainPrimitives.Generator.csproj b/src/AltaSoft.DomainPrimitives.Generator/AltaSoft.DomainPrimitives.Generator.csproj
index 0cfbd55..f39f625 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/AltaSoft.DomainPrimitives.Generator.csproj
+++ b/src/AltaSoft.DomainPrimitives.Generator/AltaSoft.DomainPrimitives.Generator.csproj
@@ -26,6 +26,7 @@
+
diff --git a/src/AltaSoft.DomainPrimitives.Generator/DomainPrimitiveGenerator.cs b/src/AltaSoft.DomainPrimitives.Generator/DomainPrimitiveGenerator.cs
index e6af9cc..2426a89 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/DomainPrimitiveGenerator.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/DomainPrimitiveGenerator.cs
@@ -119,6 +119,12 @@ private static DomainPrimitiveGlobalOptions GetGlobalOptions(AnalyzerConfigOptio
result.GenerateXmlSerialization = generateXmlSerialization;
}
+ if (analyzerOptions.GlobalOptions.TryGetValue("build_property.DomainPrimitiveGenerator_GenerateEntityFrameworkCoreValueConverters", out value)
+ && bool.TryParse(value, out var generateEntityFrameworkValueConverters))
+ {
+ result.GenerateEntityFrameworkCoreValueConverters = generateEntityFrameworkValueConverters;
+ }
+
return result;
}
}
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Executor.cs b/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
index 8f38ec7..688090e 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
@@ -33,6 +33,7 @@ internal static void Execute(
return;
var swaggerTypes = new List(typesToGenerate.Length);
+ var efCoreValueConverterTypes = new List(typesToGenerate.Length);
var cachedOperationsAttributes = new Dictionary(SymbolEqualityComparer.Default);
try
@@ -71,10 +72,20 @@ internal static void Execute(
}
if (globalOptions.GenerateSwaggerConverters)
+ {
swaggerTypes.Add(generatorData);
+ }
+
+ if (globalOptions.GenerateEntityFrameworkCoreValueConverters)
+ {
+ efCoreValueConverterTypes.Add(generatorData.TypeSymbol);
+ MethodGeneratorHelper.ProcessEntityFrameworkValueConverter(generatorData, context);
+ }
}
MethodGeneratorHelper.AddSwaggerOptions(assemblyName, swaggerTypes, context);
+
+ MethodGeneratorHelper.GenerateValueConvertersExtension(swaggerTypes.Count == 0, assemblyName, efCoreValueConverterTypes, context);
}
catch (Exception ex)
{
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
index be35527..61103b7 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
@@ -186,6 +186,92 @@ internal static void ProcessTypeConverter(GeneratorData data, SourceProductionCo
context.AddSource($"{data.ClassName}TypeConverter.g.cs", builder.ToString());
}
+ ///
+ /// Generates the value converters extension for the specified assembly name, types, and source production context.
+ ///
+ /// if assembly attribute should be added
+ /// The name of the assembly.
+ /// The list of named type symbols.
+ /// The source production context.
+ internal static void GenerateValueConvertersExtension(bool addAssemblyAttribute, string assemblyName, List types, SourceProductionContext context)
+ {
+ if (types.Count == 0)
+ return;
+
+ var builder = new SourceCodeBuilder();
+ builder.AppendSourceHeader("AltaSoft DomainPrimitives Generator");
+
+ var usings = types.ConvertAll(x => x.ContainingNamespace.ToDisplayString());
+ usings.Add("Microsoft.EntityFrameworkCore");
+ usings.AddRange(types.ConvertAll(x => x.ContainingNamespace.ToDisplayString() + ".EntityFrameworkCore.Converters"));
+
+ builder.AppendUsings(usings);
+
+ if (addAssemblyAttribute)
+ builder.AppendLine("[assembly: AltaSoft.DomainPrimitives.DomainPrimitiveAssemblyAttribute]");
+
+ var ns = string.Join(".", assemblyName.Split('.').Select(s => char.IsDigit(s[0]) ? '_' + s : s));
+ builder.AppendNamespace(ns + ".Converters.Extensions");
+
+ builder.AppendSummary($"Helper class providing methods to configure EntityFrameworkCore ValueConverters for DomainPrimitive types of {assemblyName}");
+ builder.AppendClass(false, "public static", "ModelConfigurationBuilderExt");
+
+ builder.AppendSummary("Adds EntityFrameworkCore ValueConverters for specific custom types to ensure proper mapping to EFCore ORM.");
+ builder.AppendParamDescription("configurationBuilder", "The ModelConfigurationBuilder instance to which converters are added.");
+ builder.AppendLine("public static ModelConfigurationBuilder AddDomainPrimitivePropertyConversions(this ModelConfigurationBuilder configurationBuilder)")
+ .OpenBracket();
+
+ foreach (var type in types)
+ {
+ builder.Append("configurationBuilder.Properties<").Append(type.Name).Append(">().HaveConversion<").Append(type.Name).AppendLine("ValueConverter>();");
+ }
+
+ builder.AppendLine("return configurationBuilder;");
+ builder.CloseBracket();
+
+ builder.CloseBracket();
+ context.AddSource("ModelConfigurationBuilderExt.g.cs", builder.ToString());
+ }
+
+ ///
+ /// Processes the Entity Framework value converter for the specified generator data and source production context.
+ ///
+ /// The generator data.
+ /// The source production context.
+ internal static void ProcessEntityFrameworkValueConverter(GeneratorData data, SourceProductionContext context)
+ {
+ var builder = new SourceCodeBuilder();
+
+ builder.AppendSourceHeader("AltaSoft DomainPrimitives Generator");
+
+ var usingStatements =
+ new List(8)
+ {
+ data.Namespace,
+ data.PrimitiveTypeSymbol.ContainingNamespace.ToDisplayString(),
+ "Microsoft.EntityFrameworkCore",
+ "Microsoft.EntityFrameworkCore.Storage.ValueConversion",
+ "AltaSoft.DomainPrimitives",
+ };
+
+ var converterName = data.ClassName + "ValueConverter";
+
+ builder.AppendUsings(usingStatements);
+
+ builder.AppendNamespace(data.Namespace + ".EntityFrameworkCore.Converters");
+ builder.AppendSummary($"ValueConverter for ");
+ builder.AppendClass(false, "public sealed", converterName, $"ValueConverter<{data.ClassName}, {data.PrimitiveTypeFriendlyName}>");
+
+ builder.AppendSummary($"Constructor to create {converterName}")
+ .AppendLine($"public {converterName}() : base(v=> v, v=> v)")
+ .OpenBracket()
+ .CloseBracket();
+
+ builder.CloseBracket();
+
+ context.AddSource($"{converterName}.g.cs", builder.ToString());
+ }
+
///
/// Generates code for a JsonConverter for the specified type.
///
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Models/DomainPrimitiveGlobalOptions.cs b/src/AltaSoft.DomainPrimitives.Generator/Models/DomainPrimitiveGlobalOptions.cs
index d71e4cf..6f6ef6c 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Models/DomainPrimitiveGlobalOptions.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Models/DomainPrimitiveGlobalOptions.cs
@@ -40,4 +40,12 @@ internal sealed record DomainPrimitiveGlobalOptions
/// true if XML serialization methods should be generated; otherwise, false.
///
public bool GenerateXmlSerialization { get; set; }
-}
\ No newline at end of file
+
+ ///
+ /// Gets or sets a value indicating whether Entity Framework Core value converters should be generated for Domain Primitive types.
+ ///
+ ///
+ /// true if Entity Framework Core value converters should be generated; otherwise, false.
+ ///
+ public bool GenerateEntityFrameworkCoreValueConverters { get; set; }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelpers.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelpers.cs
index db1ca96..aa06bb6 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelpers.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelpers.cs
@@ -1,9 +1,9 @@
-using AltaSoft.DomainPrimitives.Generator.Models;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using AltaSoft.DomainPrimitives.Generator.Models;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
// ReSharper disable ConvertToPrimaryConstructor
@@ -93,6 +93,10 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val
value = _options.GenerateTypeConverters.ToString();
return true;
+ case "build_property.DomainPrimitiveGenerator_GenerateEntityFrameworkCoreValueConverters":
+ value = _options.GenerateEntityFrameworkCoreValueConverters.ToString();
+ return true;
+
default:
value = null;
return false;