Skip to content

Commit

Permalink
Merge pull request #645 from SteveDunn/bson
Browse files Browse the repository at this point in the history
Add Bson serialization
  • Loading branch information
SteveDunn authored Jul 24, 2024
2 parents 441f081 + d825e30 commit fc6d240
Show file tree
Hide file tree
Showing 41 changed files with 2,873 additions and 94 deletions.
1 change: 1 addition & 0 deletions Consumers.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/SourceGeneratedFilesSweaEnabled/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
Expand Down
1 change: 1 addition & 0 deletions docs/site/Writerside/hi.tree
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<toc-element topic="Casting.md"/>
<toc-element topic="Overriding-methods.md"/>
<toc-element topic="EfCoreIntegrationHowTo.md"/>
<toc-element topic="MongoIntegrationHowTo.md"/>
<toc-element topic="Use-in-Swagger.md"/>
<toc-element topic="efcore-tips.md"/>
</toc-element>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public partial class Name
```

Another way, if you're using .NET 8 or greater, is to use `EfCoreConverter` attributes on
a marker class:
a marker class, normally in a separate project from the value objects themselves:

```c#
[EfCoreConverter<Domain.CustomerId>]
Expand Down
10 changes: 8 additions & 2 deletions docs/site/Writerside/topics/reference/Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ public enum Conversions
/// <summary>
/// Sets the SerializeFn and DeSerializeFn members in JsConfig in a static constructor.
/// </summary>
ServiceStackDotText = 1 << 7
ServiceStackDotText = 1 << 7,

/// <summary>
/// Creates a BSON serializer for each value object.
/// </summary>
Bson = 1 << 8,
}
```

Expand All @@ -93,7 +98,8 @@ Other converters/serializers are:
They are controlled by the `Conversions` enum. The following has serializers for NSJ and STJ:

```c#
[ValueObject(conversions: Conversions.NewtonsoftJson | Conversions.SystemTextJson, underlyingType: typeof(float))]
[ValueObject<float>(conversions:
Conversions.NewtonsoftJson | Conversions.SystemTextJson)]
public readonly partial struct Celsius { }
```

Expand Down
57 changes: 57 additions & 0 deletions docs/site/Writerside/topics/reference/MongoIntegrationHowTo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Integration with MongoDB

It is possible to use value objects (VOs) in MongoDB.

To generate a converter (serializer), add the `Bson` conversion in the attribute, e.g.

```c#
[ValueObject<string>(conversions: Conversions.Bson)]
public partial class Name
{
public static readonly Name NotSet = new("[NOT_SET]");
}
```

Now that the serializers are generated, you now need to register them.
Vogen generates a static class named `RegisterBsonSerializersFor[NameOfProject]`.
This static class has a static method named `TryRegister`, which registers the serializers if they're not already registered, e.g.:

```C#
BsonSerializer.TryRegisterSerializer(new CustomerIdBsonSerializer());
BsonSerializer.TryRegisterSerializer(new EnergyUsedBsonSerializer());
```
A [MongoDB example is included in the source](https://github.com/SteveDunn/Vogen/tree/main/samples/Vogen.Examples/SerializationAndConversion/MongoScenario).

Below is a walkthrough of that sample.

The sample uses MongoDB to read and write entities (a `Person`) to a MongoDB database in a testcontainer.
Note that attributes on the value objects do not specify the BSON serializer; that is specified in global config in `ModuleInitializer.cs`:

```c#
[ValueObject]
public readonly partial struct Age;

[ValueObject<string>]
public readonly partial struct Name;

public class Person
{
public Name Name { get; set; }
public Age Age { get; set; }
}
```

This simple example registers the serializers manually:
```C#
BsonSerializer.RegisterSerializer(new NameBsonSerializer());
BsonSerializer.RegisterSerializer(new AgeBsonSerializer());
```

… but it could just as easily registered them with the generated register:
```C#
BsonSerializationRegisterForVogen_Examples.TryRegister();
```

(_replace `Vogen_Examples` with the name of *your* project_)

Next, it adds a bunch of `Person` objects to the database, each containing value objects representing age and name, and then reads them back.
5 changes: 5 additions & 0 deletions samples/AotTrimmedSample/AotTrimmedSample.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
5 changes: 5 additions & 0 deletions samples/Onion/Domain/Domain.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
5 changes: 5 additions & 0 deletions samples/Onion/Infra/Infra.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
7 changes: 5 additions & 2 deletions samples/Vogen.Examples/ModuleInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using Vogen;
using Vogen.Examples.Types;

[assembly: VogenDefaults(staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon)]
[assembly: VogenDefaults(
staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon | StaticAbstractsGeneration.InstanceMethodsAndProperties,
conversions: Conversions.Default | Conversions.Bson)]

namespace Vogen.Examples;

Expand All @@ -14,7 +16,8 @@ public static class ModuleInitializer
[ModuleInitializer]
public static void Init()
{
MappingSchema.Default.SetConverter<DateTime, TimeOnly>(dt => TimeOnly.FromDateTime(dt));
MappingSchema.Default.SetConverter<DateTime, TimeOnly>(TimeOnly.FromDateTime);

SqlMapper.AddTypeHandler(new DapperDateTimeOffsetVo.DapperTypeHandler());
SqlMapper.AddTypeHandler(new DapperIntVo.DapperTypeHandler());
SqlMapper.AddTypeHandler(new DapperStringVo.DapperTypeHandler());
Expand Down
8 changes: 3 additions & 5 deletions samples/Vogen.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ namespace Vogen.Examples
class Program
{
// ReSharper disable once UnusedParameter.Local
static Task Main(string[] args)
static async Task Main(string[] args)
{
var scenarioTypes = typeof(Program).Assembly.GetTypes()
.Where(t => typeof(IScenario).IsAssignableFrom(t) && t != typeof(IScenario)).ToList();

foreach (var eachScenarioType in scenarioTypes)
{
var instance = (IScenario)Activator.CreateInstance(eachScenarioType);
var instance = (IScenario)Activator.CreateInstance(eachScenarioType)!;
WriteBanner(instance);

instance!.Run();
await instance.Run();
}

Console.WriteLine("Finished");

return Task.CompletedTask;
}

private static void WriteBanner(IScenario scenario)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bogus;
using JetBrains.Annotations;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
using Testcontainers.MongoDb;

namespace Vogen.Examples.SerializationAndConversion.Mongo;


[ValueObject]
public readonly partial struct Age;

[ValueObject<string>]
public readonly partial struct Name;

[UsedImplicitly]
public class Person
{
[BsonId]
public ObjectId Id { get; set; }
public Name Name { get; set; }
public Age Age { get; set; }
}

[UsedImplicitly]
public class MongoScenario : IScenario
{
public async Task Run()
{
string runnerOs = Environment.GetEnvironmentVariable("RUNNER_OS");

bool isLocalOrLinuxOnGitHub = string.IsNullOrEmpty(runnerOs) || runnerOs == "Linux";

if (!isLocalOrLinuxOnGitHub)
{
Console.WriteLine("Skipping because not running locally or on Linux on a GitHub action.");
return;
}

MongoDbContainer container = new MongoDbBuilder().WithImage("mongo:latest").Build();

await container.StartAsync();

var client = new MongoClient(container.GetConnectionString());

var database = client.GetDatabase("testDatabase");
var collection = database.GetCollection<Person>("peopleCollection");

BsonSerializer.RegisterSerializer(new NameBsonSerializer());
BsonSerializer.RegisterSerializer(new AgeBsonSerializer());

// or, use the generated one for all value objects...
// BsonSerializationRegisterForVogen_Examples.TryRegister();

var personFaker = new Faker<Person>()
.RuleFor(p => p.Name, f => Name.From(f.Name.FirstName()))
.RuleFor(p => p.Age, f => Age.From(DateTime.Now.Year - f.Person.DateOfBirth.Year));

foreach (Person eachPerson in personFaker.Generate(10))
{
await collection.InsertOneAsync(eachPerson);
}

Console.WriteLine("Inserted people... Now finding them...");

IAsyncCursor<Person> people = await collection.FindAsync("{}");
await people.ForEachAsync((person) => Console.WriteLine($"{person.Name} is {person.Age}"));

await container.DisposeAsync();
}
}



// Note, if you don't want any generated BSON serializers, you can specify your own generic one like the one below.
// Be aware that you'll need specify static abstracts generation in global config for this to work:
// [assembly: VogenDefaults(
// staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon | StaticAbstractsGeneration.InstanceMethodsAndProperties,
// conversions: Conversions.Default | Conversions.Bson)]

// ReSharper disable once UnusedType.Global
public class BsonVogenSerializer<TValue, TUnderlyingTypeValue>
: SerializerBase<TValue> where TValue : IVogen<TValue, TUnderlyingTypeValue>
{
private readonly IBsonSerializer<TUnderlyingTypeValue> _serializer = BsonSerializer.LookupSerializer<TUnderlyingTypeValue>();

public override TValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
TValue.From(_serializer.Deserialize(context, args));

public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value) =>
_serializer.Serialize(context, args, value.Value);
}
4 changes: 4 additions & 0 deletions samples/Vogen.Examples/Vogen.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -17,11 +18,14 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0-rc.1.22426.7" />
<PackageReference Include="MongoDB.Driver" Version="2.27.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="linq2db" Version="3.7.0" />
<PackageReference Include="ServiceStack.Text" Version="8.2.2" />
<PackageReference Include="Testcontainers" Version="3.9.0" />
<PackageReference Include="Testcontainers.MongoDb" Version="3.9.0" />
</ItemGroup>

<ItemGroup Condition=" '$(UseLocallyBuiltPackage)' != ''">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
7 changes: 6 additions & 1 deletion src/Vogen.SharedTypes/Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,10 @@ public enum Conversions
/// <summary>
/// Sets the SerializeFn and DeSerializeFn members in JsConfig in a static constructor.
/// </summary>
ServiceStackDotText = 1 << 7
ServiceStackDotText = 1 << 7,

/// <summary>
/// Creates a BSON serializer for each value object.
/// </summary>
Bson = 1 << 8
}
2 changes: 2 additions & 0 deletions src/Vogen/ValueObjectGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ private static void Execute(
WriteOpenApiSchemaCustomizationCode.WriteIfNeeded(globalConfig, spc, compilation, workItems);

WriteEfCoreSpecs.WriteIfNeeded(spc, compilation, efCoreConverterSpecs);

WriteBsonSerializers.WriteIfNeeded(spc, compilation, workItems);

if (workItems.Count > 0)
{
Expand Down
Loading

0 comments on commit fc6d240

Please sign in to comment.