Skip to content

Commit

Permalink
FEATURE: change DbContext to have injectable Model Creator (#423)
Browse files Browse the repository at this point in the history
* remove default ctor dbcontext

* constrain dbcontext options ctor

* add base OnModelCreating invoke

* add injectable db model creation

* Update KeyedModelInterfaceGeneratorProcessor.cs

* add created, modified, rowversion to ef model

* correct typo in datetimeoffset

* add initial logic for ms sql model creator

* update sonar token arg

* fix generation around interface implementation

* add unit test

* fix namespacing of dbcontext
  • Loading branch information
dpvreony authored Dec 22, 2023
1 parent 73da549 commit 2281b8e
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 107 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
fetch-depth: 0

# java is used by sonar scanner
- name: Setup Java 11
- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
Expand Down Expand Up @@ -67,7 +67,7 @@ jobs:
- name: Run Sonar Scanner begin
if: env.SONAR_TOKEN != '' && env.SONAR_PROJECT_KEY != '' && env.SONAR_ORGANISATION_KEY != ''
run: |
dotnet sonarscanner begin /k:"${{ env.SONAR_PROJECT_KEY }}" /d:sonar.login="${{ env.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /o:"${{ env.SONAR_ORGANISATION_KEY }}" /d:sonar.cs.opencover.reportsPaths="artifacts/opencover/**/*.xml"
dotnet sonarscanner begin /k:"${{ env.SONAR_PROJECT_KEY }}" /d:sonar.token="${{ env.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /o:"${{ env.SONAR_ORGANISATION_KEY }}" /d:sonar.cs.opencover.reportsPaths="artifacts/opencover/**/*.xml"
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<PackageReference Include="Whipstaff.AspNetCore" Version="7.1.3" />
<PackageReference Include="Whipstaff.EntityFramework" Version="7.1.3" />
<PackageReference Include="Whipstaff.Wpf" Version="7.1.3" />
<PackageReference Include="Whipstaff.Wpf.Mahapps" Version="7.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,16 @@ private MemberDeclarationSyntax[] GetMembers(string className, EntityGenerationM
//var constructorArguments = GetConstructorArguments();
//var baseArguments = GetBaseConstructorArguments();

//var fields = GetFieldDeclarations(constructorArguments, entityName);
//if (fields != null && fields.Length > 0)
//{
// result.AddRange(fields);
//}
var fields = GetFieldDeclarations(className);
if (fields != null && fields.Length > 0)
{
result.AddRange(fields);
}

//if (constructorArguments != null && constructorArguments.Count > 0)
//{
// result.Add(GenerateConstructor(className, constructorArguments, entityName, baseArguments));
//}
result.Add(GetDefaultConstructor(className));
result.Add(GetConstructorWithDbOptions(className));

var properties = GetPropertyDeclarations(generationModelEntityGenerationModel);
Expand All @@ -85,20 +84,13 @@ private MemberDeclarationSyntax[] GetMembers(string className, EntityGenerationM
return result.ToArray();
}

private ConstructorDeclarationSyntax GetDefaultConstructor(string className)
{
var body = new List<StatementSyntax>();

var declaration = SyntaxFactory.ConstructorDeclaration(className)
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddBodyStatements(body.ToArray());

return declaration;
}

private ConstructorDeclarationSyntax GetConstructorWithDbOptions(string className)
{
var parameters = GetParams(new []{ "DbContextOptions dbContextOptions"});
var parameters = GetParams(new []
{
$"DbContextOptions<{className}> dbContextOptions",
$"Func<Whipstaff.EntityFramework.ModelCreation.IModelCreator<{className}>> modelCreatorFunc"
});

var seperatedSyntaxList = new SeparatedSyntaxList<ArgumentSyntax>();

Expand All @@ -112,7 +104,29 @@ private ConstructorDeclarationSyntax GetConstructorWithDbOptions(string classNam
SyntaxKind.BaseConstructorInitializer,
baseInitializerArgumentList);

var body = new List<StatementSyntax>();
var body = new List<StatementSyntax>
{
RoslynGenerationHelpers.GetNullGuardCheckSyntax("modelCreatorFunc"),
};

var left = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.ThisExpression(),
SyntaxFactory.Token(SyntaxKind.DotToken),
SyntaxFactory.IdentifierName(SyntaxFactory.Identifier($"_modelCreatorFunc")));

var right = SyntaxFactory.IdentifierName(SyntaxFactory.Identifier("modelCreatorFunc"));

var assignment = SyntaxFactory.ExpressionStatement(
SyntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
left,
SyntaxFactory.Token(SyntaxKind.EqualsToken),
right),
SyntaxFactory.Token(SyntaxKind.SemicolonToken)
);

body.Add(assignment);

var declaration = SyntaxFactory.ConstructorDeclaration(className)
.WithParameterList(parameters)
Expand Down Expand Up @@ -168,32 +182,21 @@ private PropertyDeclarationSyntax GetPropertyDeclaration(
return result;
}

//private MemberDeclarationSyntax[] GetFieldDeclarations(
// IList<Tuple<Func<string, string>, string, Accessibility>> constructorArguments,
// string entityName)
//{
// if (constructorArguments == null || constructorArguments.Count < 1)
// {
// return null;
// }

// var result = new List<MemberDeclarationSyntax>();

// foreach (var constructorArgument in constructorArguments)
// {
// var fieldType = constructorArgument.Item1(entityName);

// var fieldDeclaration = SyntaxFactory.FieldDeclaration(
// SyntaxFactory.VariableDeclaration(
// SyntaxFactory.ParseTypeName(fieldType),
// SyntaxFactory.SeparatedList(new[] { SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier($"_{constructorArgument.Item2}")) })
// ))
// .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword));
// result.Add(fieldDeclaration);
// }

// return result.ToArray();
//}
private MemberDeclarationSyntax[] GetFieldDeclarations(string className)
{

var result = new List<MemberDeclarationSyntax>();

var fieldDeclaration = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.ParseTypeName($"Func<Whipstaff.EntityFramework.ModelCreation.IModelCreator<{className}>>"),
SyntaxFactory.SeparatedList(new[] { SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier($"_modelCreatorFunc")) })
))
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword));
result.Add(fieldDeclaration);

return result.ToArray();
}

private MemberDeclarationSyntax GetClassDeclarationSyntax(EntityFrameworkDbContextGenerationModel generationModelEntityGenerationModel, string suffix)
{
Expand Down Expand Up @@ -342,26 +345,35 @@ private MemberDeclarationSyntax GetOnModelCreatingMethodDeclaration(EntityGenera
{
var methodName = $"OnModelCreating";

var body = generationModelEntityGenerationModel.Select(GetApplyConfigurationInvocationDeclaration).ToArray();
var body = new List<StatementSyntax>
{
RoslynGenerationHelpers.GetMethodOnVariableInvocationSyntax(
"base",
methodName,
new [] { "modelBuilder" },
false),
RoslynGenerationHelpers.GetVariableAssignmentFromFuncVariableInvocationSyntax(
"modelCreator",
"_modelCreatorFunc",
Array.Empty<string>(),
false),
RoslynGenerationHelpers.GetMethodOnVariableInvocationSyntax(
"modelCreator",
"CreateModel",
new [] { "modelBuilder" },
false),
};

var parameters = GetParams(new []{ $"Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder"});

var inheritDocSyntaxTrivia = RoslynGenerationHelpers.GetInheritDocSyntaxTrivia();
var returnType = SyntaxFactory.ParseTypeName("void");
var declaration = SyntaxFactory.MethodDeclaration(returnType, methodName)
.WithParameterList(parameters)
.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword), SyntaxFactory.Token(SyntaxKind.OverrideKeyword))
.AddBodyStatements(body.ToArray());
.AddBodyStatements(body.ToArray())
.WithLeadingTrivia(inheritDocSyntaxTrivia);
return declaration;
}

private StatementSyntax GetApplyConfigurationInvocationDeclaration(EntityGenerationModel entityGenerationModel)
{
var invokeExpression = RoslynGenerationHelpers.GetMethodOnVariableInvocationSyntax("modelBuilder",
"ApplyConfiguration",
new[] {$"new EntityTypeConfigurations.{entityGenerationModel.ClassName}EntityTypeConfiguration()"},
false);

return invokeExpression;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -518,11 +518,11 @@ private ExpressionSyntax CheckRequiredMethodDeclaration(ExpressionSyntax fluentA

private void DoPrimaryKeyMethodDeclaration(List<StatementSyntax> body)
{
var statement =
RoslynGenerationHelpers.GetMethodOnVariableInvocationSyntax(
var statement = RoslynGenerationHelpers.GetMethodOnVariableInvocationSyntax(
"builder",
"HasKey",
new[] {$"x => x.Id"}, false);
new[] {$"x => x.Id"},
false);
body.Add(statement);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Dhgms.Nucleotide.Generators.Features.Common.AttributeGenerators;
using Dhgms.Nucleotide.Generators.GeneratorProcessors;
using Dhgms.Nucleotide.Generators.PropertyInfo;
Expand All @@ -24,7 +25,25 @@ public sealed class EntityFrameworkModelGeneratorProcessor : BaseClassLevelCodeG
///<inheritdoc />
protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclarations(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
var inheritDocSyntaxTrivia = RoslynGenerationHelpers.GetInheritDocSyntaxTrivia();
var inheritDocSyntaxTrivia = RoslynGenerationHelpers.GetInheritDocSyntaxTrivia().ToArray();
var datetimeOffSetType = SyntaxFactory.ParseTypeName("System.DateTimeOffset");
var ulongType = SyntaxFactory.ParseTypeName("ulong");

yield return RoslynGenerationHelpers.GetPropertyDeclarationSyntax(
datetimeOffSetType,
$"Created",
inheritDocSyntaxTrivia);

yield return RoslynGenerationHelpers.GetPropertyDeclarationSyntax(
datetimeOffSetType,
$"Modified",
inheritDocSyntaxTrivia);

yield return RoslynGenerationHelpers.GetPropertyDeclarationSyntax(
ulongType,
$"RowVersion",
inheritDocSyntaxTrivia);

if (entityGenerationModel?.ParentEntityRelationships != null)
{
foreach (var referencedByEntityGenerationModel in entityGenerationModel.ParentEntityRelationships)
Expand Down Expand Up @@ -145,6 +164,9 @@ protected override string GetBaseClass(EntityFrameworkModelEntityGenerationModel
///<inheritdoc />
protected override IEnumerable<string> GetImplementedInterfaces(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
yield return $"global::Whipstaff.Core.Entities.ILongRowVersion";
yield return $"global::Whipstaff.Core.Entities.IModifiable";

if (entityGenerationModel?.ParentEntityRelationships != null)
{
foreach (var referencedByEntityGenerationModel in entityGenerationModel.ParentEntityRelationships)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2020 DHGMS Solutions and Contributors. All rights reserved.
// DHGMS Solutions and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Dhgms.Nucleotide.Generators.Generators;

namespace Dhgms.Nucleotide.Generators.Features.EntityFramework
{
/// <summary>
/// Code Generator for Entity Framework Injectable Model Creator.
/// </summary>
public abstract class EntityFrameworkMsSqlModelCreatorGenerator : BaseGenerator<EntityFrameworkDbContextFeatureFlags, EntityFrameworkMsSqlModelCreatorGeneratorProcessor, EntityFrameworkDbContextGenerationModel>
{
///<inheritdoc />
protected override string GetNamespace()
{
return "ModelCreation";
}
}
}
Loading

0 comments on commit 2281b8e

Please sign in to comment.