Update template working

This commit is contained in:
CodeLiturgy 2022-08-29 01:41:05 +01:00
parent f419c0f8ea
commit 654a54d5ef
8 changed files with 143 additions and 114 deletions

View File

@ -19,14 +19,12 @@ namespace MapTo
protected EfAddGeneratorContext(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax memberSyntax,
string addSourceTemplate)
MemberDeclarationSyntax memberSyntax)
{
Compilation = compilation;
_ignoredNamespaces = new();
Diagnostics = ImmutableArray<Diagnostic>.Empty;
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
SourceTemplate = addSourceTemplate;
SourceGenerationOptions = sourceGenerationOptions;
MemberSyntax = memberSyntax;
@ -34,8 +32,7 @@ namespace MapTo
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
}
public string SourceTemplate { get; }
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
@ -54,12 +51,11 @@ namespace MapTo
public static EfAddGeneratorContext Create(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax typeSyntax,
string addSourceTemplate)
MemberDeclarationSyntax typeSyntax)
{
EfAddGeneratorContext context = typeSyntax switch
{
PropertyDeclarationSyntax => new EfAddGeneratorContext(compilation, sourceGenerationOptions, typeSyntax, addSourceTemplate),
PropertyDeclarationSyntax => new EfAddGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
_ => throw new ArgumentOutOfRangeException()
};
@ -94,6 +90,13 @@ namespace MapTo
return sourceSymbol;
}
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = GetEntityStringLiteralSymbol(attributeData, semanticModel);
return sourceSymbol;
}
protected ImmutableArray<INamedTypeSymbol> GetEntityTypeSymbols(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{
if (attributeSyntax is null)
@ -123,6 +126,25 @@ namespace MapTo
return resultList.ToImmutableArray();
}
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{
if (attributeSyntax is null)
{
return new ImmutableArray<LiteralExpressionSyntax>(){};
}
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
var sourceTypeExpressionSyntax = descendentNodes
.OfType<LiteralExpressionSyntax>()
.ToImmutableArray();
return sourceTypeExpressionSyntax;
}
protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult)
{
@ -194,6 +216,16 @@ namespace MapTo
return null;
}
var updateConfig = ExtractEfUpdateMethodsModel(semanticModel, entityTypeName, entityTypeFullName);
if (updateConfig == null)
{
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(MemberSyntax.GetLocation()));
return null;
}
return new EfMethodsModel(
SourceGenerationOptions,
contextNamespace,
@ -203,6 +235,7 @@ namespace MapTo
entityTypeFullName,
entityTypeName,
addConfig,
updateConfig,
Usings);
}
@ -233,6 +266,48 @@ namespace MapTo
return new EfAddMethodsModel(createTypeFullName, createTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName);
}
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, string entityTypeName, string entityTypeFullName)
{
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyPropertyName = GetEntityStringLiteralSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
if (efAddAttributeTypeSymbols == null) return null;
string updateTypeIdentifierName = entityTypeName;
string updateTypeFullName = entityTypeFullName;
string returnTypeIdentifierName = entityTypeName;
string returnTypeFullName = entityTypeFullName;
string keyPropertyTypeFullName = "int";
if (efAddAttributeTypeSymbols.Length > 0)
{
updateTypeIdentifierName = efAddAttributeTypeSymbols[0].Name;
updateTypeFullName = efAddAttributeTypeSymbols[0].ToDisplayString();
}
if (efAddAttributeTypeSymbols.Length > 0)
{
returnTypeIdentifierName = efAddAttributeTypeSymbols[1].Name;
returnTypeFullName = efAddAttributeTypeSymbols[1].ToDisplayString();
}
if (efAddAttributeTypeSymbols.Length > 2)
{
keyPropertyTypeFullName = efAddAttributeTypeSymbols[2].ToDisplayString();
}
return new EfUpdateMethodsModel(updateTypeFullName, updateTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName, keyPropertyName, keyPropertyTypeFullName);
}
private static ITypeSymbol GetEntityTypeData(MemberDeclarationSyntax memberDeclarationSyntax, SemanticModel? semanticModel = null)
{

View File

@ -39,7 +39,9 @@ namespace MapTo
var options = SourceGenerationOptions.From(context);
var compilation = context.Compilation
.AddSource(ref context, EfAddMethodsAttributeSource.Generate(options));
.AddSource(ref context, EfAddMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfUpdateMethodsAttributeSource.Generate(options));
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateMembers.Any())
{
@ -59,17 +61,18 @@ namespace MapTo
foreach (var candidateMember in candidateMembers)
{
string addSourceTemplate = string.Empty;
if (context.AdditionalFiles.Length > 0)
{
addSourceTemplate = context.AdditionalFiles
.FirstOrDefault()?
string addSourceTemplate = context.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains("AddToEntityTemplate"))?
.GetText()?
.ToString() ?? string.Empty;
}
var mappingContext = EfAddGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax, addSourceTemplate);
string updateSourceTemplate = context.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains("UpdateEntityTemplate"))?
.GetText()?
.ToString() ?? string.Empty;
var mappingContext = EfAddGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
@ -80,7 +83,7 @@ namespace MapTo
var (source, hintName) = candidateMember.MemberDeclarationSyntax switch
{
PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate),
PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate, updateSourceTemplate),
_ => throw new ArgumentOutOfRangeException()
};

View File

@ -35,11 +35,10 @@ namespace MapTo.Extensions
{
var attributeLists = typeDeclarationSyntax.AttributeLists;
var selection = attributeLists
.SelectMany(al => al.Attributes);
var result = selection
.FirstOrDefault();
.SelectMany(al => al.Attributes)
.FirstOrDefault(x => x.Name.ToString().Contains(attributeName));
return result;
return selection;
}
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>

View File

@ -79,10 +79,12 @@ namespace MapTo
) : IEfMethodsConfiguration;
internal record EfUpdateMethodsModel(
string CreateTypeFullName,
string CreateTypeIdentifierName,
string UpdateTypeFullName,
string UpdateTypeIdentifierName,
string ReturnTypeFullName,
string ReturnTypeIdentifierName
string ReturnTypeIdentifierName,
string keyPropertyName,
string keyFullTypeName
): IEfMethodsConfiguration;
@ -95,6 +97,7 @@ namespace MapTo
string EntityTypeFullName,
string EntityTypeIdentifierName,
EfAddMethodsModel AddMethodsModel,
EfUpdateMethodsModel UpdateMethodsModel,
ImmutableArray<string> Usings
);

View File

@ -32,7 +32,7 @@ namespace MapTo.Sources
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type createDto = null, Type returnType = null)")
.WriteLine($"public {AttributeName}Attribute(Type createType = null, Type returnType = null)")
.WriteOpeningBracket()
.WriteClosingBracket()
.WriteLine();

View File

@ -9,7 +9,7 @@ namespace MapTo.Sources
{
internal static class EfMethodsSource
{
internal static SourceCode Generate(EfMethodsModel model, string addSourceTemplate)
internal static SourceCode Generate(EfMethodsModel model, string addSourceTemplate, string updateSourceTemplate)
{
using var builder = new SourceBuilder();
@ -21,6 +21,10 @@ namespace MapTo.Sources
builder
.WriteUsings(model.Usings)
.WriteLine("using Microsoft.EntityFrameworkCore;")
.WriteLine("using System.Linq;")
.WriteLine("using System.Linq.Expressions;")
.WriteLine("using System.Threading.Tasks;")
.WriteLine()
// Namespace declaration
.WriteLine($"namespace {model.Namespace}")
@ -36,6 +40,12 @@ namespace MapTo.Sources
contextFullName,
entityTypeFullName,
propertyName)
.EfAddUpdateEntityMethod(model, updateSourceTemplate,
entityTypeName,
contextFullName,
entityTypeFullName,
propertyName)
// End class declaration
.WriteClosingBracket()
.WriteLine()
@ -93,45 +103,53 @@ namespace MapTo.Sources
return builder;
}
private static SourceBuilder EfAddUpdateEntityMethod(this SourceBuilder builder, EfMethodsModel model, string addSourceTemplate, string entityTypeName,
private static SourceBuilder EfAddUpdateEntityMethod(this SourceBuilder builder, EfMethodsModel model, string updateSourceTemplate, string entityTypeName,
string contextFullName, string entityTypeFullName, string propertyName)
{
var returnTypeFullName = model.AddMethodsModel.ReturnTypeFullName;
var updateTypeFullName = model.AddMethodsModel.CreateTypeFullName;
var newEntityVarName = $"new{model.EntityTypeIdentifierName}";
var toCreateVarName = $"{model.EntityTypeIdentifierName.ToCamelCase()}ToCreate";
if (!addSourceTemplate.IsEmpty())
var returnTypeFullName = model.UpdateMethodsModel.ReturnTypeFullName;
var updateTypeFullName = model.UpdateMethodsModel.UpdateTypeFullName;
var updateVarName = $"{model.EntityTypeIdentifierName.ToCamelCase()}ToUpdate";
var keyPropertyName = model.UpdateMethodsModel.keyPropertyName;
var keyTypeFullName = model.UpdateMethodsModel.keyFullTypeName;
var existingVarName = entityTypeName.ToCamelCase();
var keyVarName = entityTypeName.ToCamelCase() + model.UpdateMethodsModel.keyPropertyName;
if (!updateSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(addSourceTemplate);
var templateToSourceBuilder = new StringBuilder(updateSourceTemplate);
templateToSourceBuilder
.Replace("{entityTypeName}", entityTypeName)
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{updateTypeFullName}", updateTypeFullName)
.Replace("{updateVarName}", updateVarName)
.Replace("{contextFullName}", contextFullName)
.Replace("{propertyName}", propertyName);
.Replace("{propertyName}", propertyName)
.Replace("{keyTypeFullName}", keyTypeFullName)
.Replace("{keyPropertyName}", keyPropertyName)
.Replace("{keyVarName}", keyVarName)
.Replace("{existingEntityVarName}", existingVarName);
builder
.Write(templateToSourceBuilder.ToString());
}
if (addSourceTemplate.IsEmpty())
if (updateSourceTemplate.IsEmpty())
{
builder
.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {model.AddMethodsModel.ReturnTypeFullName}) Add{entityTypeName}(")
.WriteLine($"public static (bool, {returnTypeFullName}) Update{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,")
.WriteLine($"{updateTypeFullName} {toCreateVarName})")
.WriteLine($"{updateTypeFullName} {updateVarName},")
.WriteLine($"{keyTypeFullName} {keyVarName})")
.WriteOpeningBracket()
.WriteLine($"var {newEntityVarName} = new {model.EntityTypeFullName}({toCreateVarName});")
.WriteLine($"dbContext.{propertyName}.Add({newEntityVarName});")
.WriteLine($"var {existingVarName} = dbContext.{propertyName}.FirstOrDefault(x => x.{keyPropertyName} == {keyVarName});")
.WriteLine($"if ({existingVarName} == null) return (false, null);")
.WriteLine($"var success = dbContext.SaveChanges() >= 0;")
.WriteLine($"return (success, new {returnTypeFullName}({newEntityVarName}));")
.WriteLine($"return (success, new {returnTypeFullName}({existingVarName}));")
.WriteClosingBracket();
builder
.WriteLine();
}
builder.Indent();
return builder;
}

View File

@ -31,7 +31,7 @@ namespace MapTo.Sources
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type updateType = null, Type returnType = null, string keyPropertyMemberName, Type keyPropertyMemberType)")
.WriteLine($"public {AttributeName}Attribute(Type updateType, Type returnType, string keyPropertyMemberName, Type keyPropertyMemberType)")
.WriteOpeningBracket()
.WriteClosingBracket()
.WriteLine();

View File

@ -1,69 +0,0 @@
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
/// <summary>
/// UpdateData Attribute Source
/// </summary>
public class UpdateDataAttributeSource
{
internal const string AttributeName = "UpdateData";
internal const string AttributeClassName = AttributeName + "Attribute";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
internal static SourceCode Generate(SourceGenerationOptions options)
{
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
.WriteLine("using System;")
.WriteLine()
.WriteLine($"namespace {RootNamespace}")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Generates CRUD functions to be used with the specified database context and entity.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
.WriteLine("/// </summary>")
.WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>");
}
builder
.WriteLine($"public {AttributeName}Attribute(Type databaseContextType)")
.WriteOpeningBracket()
.WriteLine("DatabaseContextType = databaseContextType;")
.WriteClosingBracket()
.WriteLine();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Gets the type to map from.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("public Type DatabaseContextType { get; }")
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}