From e92946e9ec1ef65151b679b794cfc67b41017d06 Mon Sep 17 00:00:00 2001 From: Paul Welter Date: Mon, 16 Sep 2024 15:36:17 -0500 Subject: [PATCH] add GenerateReaderAttribute --- .../DataReaderFactoryGenerator.cs | 128 ++---- .../DataReaderFactoryWriter.cs | 100 ++--- .../GenerateAttributeGenerator.cs | 56 +++ .../Internal/EquatableArray.cs | 72 ---- .../Internal/HashCode.cs | 385 ------------------ .../Internal/IsExternalInit.cs | 17 - .../IsExternalInit.cs | 6 + .../Models/EntityClass.cs | 64 +-- .../Models/EntityContext.cs | 52 +-- .../Models/EntityProperty.cs | 69 +--- .../Models/EquatableArray.cs | 62 +++ .../TableAttributeGenerator.cs | 54 +++ .../Attributes/GenerateReaderAttribute.cs | 14 + test/FluentCommand.Entities/Brand.cs | 13 + .../DataReaderFactoryWriterTests.cs | 3 +- ...erFactoryWriterTests.Generate.verified.txt | 48 +-- 16 files changed, 318 insertions(+), 825 deletions(-) create mode 100644 src/FluentCommand.Generators/GenerateAttributeGenerator.cs delete mode 100644 src/FluentCommand.Generators/Internal/EquatableArray.cs delete mode 100644 src/FluentCommand.Generators/Internal/HashCode.cs delete mode 100644 src/FluentCommand.Generators/Internal/IsExternalInit.cs create mode 100644 src/FluentCommand.Generators/IsExternalInit.cs create mode 100644 src/FluentCommand.Generators/Models/EquatableArray.cs create mode 100644 src/FluentCommand.Generators/TableAttributeGenerator.cs create mode 100644 src/FluentCommand/Attributes/GenerateReaderAttribute.cs create mode 100644 test/FluentCommand.Entities/Brand.cs diff --git a/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs b/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs index caef3f6f..38ecb4cf 100644 --- a/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs +++ b/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs @@ -1,50 +1,20 @@ -using System; using System.Collections.Immutable; -using System.Reflection; -using System.Xml.Linq; -using FluentCommand.Generators.Internal; using FluentCommand.Generators.Models; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace FluentCommand.Generators; -[Generator(LanguageNames.CSharp)] -public class DataReaderFactoryGenerator : IIncrementalGenerator +public abstract class DataReaderFactoryGenerator { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var provider = context.SyntaxProvider.ForAttributeWithMetadataName( - fullyQualifiedMetadataName: "System.ComponentModel.DataAnnotations.Schema.TableAttribute", - predicate: SyntacticPredicate, - transform: SemanticTransform - ) - .Where(static context => context is not null); - - // Emit the diagnostics, if needed - var diagnostics = provider - .Select(static (item, _) => item.Diagnostics) - .Where(static item => item.Count > 0); - - context.RegisterSourceOutput(diagnostics, ReportDiagnostic); - - var entityClasses = provider - .Select(static (item, _) => item.EntityClass) - .Where(static item => item is not null); - - context.RegisterSourceOutput(entityClasses, Execute); - } - - private static void ReportDiagnostic(SourceProductionContext context, EquatableArray diagnostics) + protected static void ReportDiagnostic(SourceProductionContext context, EquatableArray diagnostics) { foreach (var diagnostic in diagnostics) context.ReportDiagnostic(diagnostic); } - private static void Execute(SourceProductionContext context, EntityClass entityClass) + protected static void WriteSource(SourceProductionContext context, EntityClass entityClass) { var qualifiedName = entityClass.EntityNamespace is null ? entityClass.EntityName @@ -55,23 +25,12 @@ private static void Execute(SourceProductionContext context, EntityClass entityC context.AddSource($"{qualifiedName}DataReaderExtensions.g.cs", source); } - private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken) - { - return syntaxNode is ClassDeclarationSyntax - { AttributeLists.Count: > 0 } classDeclaration - && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword) - && !classDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword) - || syntaxNode is RecordDeclarationSyntax - { AttributeLists.Count: > 0 } recordDeclaration - && !recordDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword) - && !recordDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword); - } - - private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + protected static EntityContext CreateContext(Location location, INamedTypeSymbol targetSymbol) { - if (context.TargetSymbol is not INamedTypeSymbol targetSymbol) + if (targetSymbol == null) return null; + var fullyQualified = targetSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString(); var className = targetSymbol.Name; @@ -84,10 +43,11 @@ private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext c if (mode == InitializationMode.ObjectInitializer) { var propertyArray = propertySymbols - .Select(p => CreateProperty(p)); + .Select(p => CreateProperty(p)) + .ToArray(); - var entity = new EntityClass(mode, classNamespace, className, propertyArray); - return new EntityContext(entity, Enumerable.Empty()); + var entity = new EntityClass(mode, fullyQualified, classNamespace, className, propertyArray); + return new EntityContext(entity, []); } // constructor initialization @@ -99,7 +59,7 @@ private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext c { var constructorDiagnostic = Diagnostic.Create( DiagnosticDescriptors.InvalidConstructor, - context.TargetNode.GetLocation(), + location, propertySymbols.Count, className ); @@ -121,7 +81,7 @@ private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext c { var constructorDiagnostic = Diagnostic.Create( DiagnosticDescriptors.InvalidConstructorParameter, - context.TargetNode.GetLocation(), + location, propertySymbol.Name, className ); @@ -135,11 +95,11 @@ private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext c properties.Add(property); } - var entityClass = new EntityClass(mode, classNamespace, className, properties); + var entityClass = new EntityClass(mode, fullyQualified, classNamespace, className, properties); return new EntityContext(entityClass, diagnostics); } - private static List GetProperties(INamedTypeSymbol targetSymbol) + protected static List GetProperties(INamedTypeSymbol targetSymbol) { var properties = new Dictionary(); @@ -164,7 +124,7 @@ private static List GetProperties(INamedTypeSymbol targetSymbol return properties.Values.ToList(); } - private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string parameterName = null) + protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string parameterName = null) { var propertyType = propertySymbol.Type.ToDisplayString(); var propertyName = propertySymbol.Name; @@ -234,35 +194,7 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str parameterName); } - private static bool IsIncluded(IPropertySymbol propertySymbol) - { - var attributes = propertySymbol.GetAttributes(); - if (attributes.Length > 0 && attributes.Any( - a => a.AttributeClass is - { - Name: "NotMappedAttribute", - ContainingNamespace: - { - Name: "Schema", - ContainingNamespace: - { - Name: "DataAnnotations", - ContainingNamespace: - { - Name: "ComponentModel", - ContainingNamespace.Name: "System" - } - } - } - })) - { - return false; - } - - return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public; - } - - private static string GetColumnName(ImmutableArray attributes) + protected static string GetColumnName(ImmutableArray attributes) { var columnAttribute = attributes .FirstOrDefault(a => a.AttributeClass is @@ -293,4 +225,32 @@ private static string GetColumnName(ImmutableArray attributes) return null; } + + protected static bool IsIncluded(IPropertySymbol propertySymbol) + { + var attributes = propertySymbol.GetAttributes(); + if (attributes.Length > 0 && attributes.Any( + a => a.AttributeClass is + { + Name: "NotMappedAttribute", + ContainingNamespace: + { + Name: "Schema", + ContainingNamespace: + { + Name: "DataAnnotations", + ContainingNamespace: + { + Name: "ComponentModel", + ContainingNamespace.Name: "System" + } + } + } + })) + { + return false; + } + + return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public; + } } diff --git a/src/FluentCommand.Generators/DataReaderFactoryWriter.cs b/src/FluentCommand.Generators/DataReaderFactoryWriter.cs index 295b889b..0226666b 100644 --- a/src/FluentCommand.Generators/DataReaderFactoryWriter.cs +++ b/src/FluentCommand.Generators/DataReaderFactoryWriter.cs @@ -58,18 +58,14 @@ private static void WriteQuerySingleEntityTask(IndentedStringBuilder codeBuilder codeBuilder .AppendLine("/// ") .Append("/// Executes the query and returns the first row in the result as a object.") .AppendLine("/// ") .AppendLine("/// The for this extension method.") .AppendLine("/// The cancellation instruction.") .AppendLine("/// ") .Append("/// A instance of if row exists; otherwise null.") .AppendLine("/// ") .Append("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"") @@ -78,25 +74,19 @@ private static void WriteQuerySingleEntityTask(IndentedStringBuilder codeBuilder .Append(ThisAssembly.InformationalVersion) .AppendLine("\")]") .Append("public static global::System.Threading.Tasks.Task<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .Append("> QuerySingle") .AppendLine("Async(") .IncrementIndent() .AppendLine("this global::FluentCommand.IDataQueryAsync dataQuery,") .AppendLine("global::System.Threading.CancellationToken cancellationToken = default)") .Append("where TEntity : ") - .Append(entity.EntityNamespace) - .Append(".") - .AppendLine(entity.EntityName) + .AppendLine(entity.FullyQualified) .DecrementIndent() .AppendLine("{") .IncrementIndent() .Append("return dataQuery.QuerySingleAsync<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine(">(") .IncrementIndent() .Append("factory: ") @@ -120,18 +110,14 @@ private static void WriteQueryEntityTask(IndentedStringBuilder codeBuilder, Enti codeBuilder .AppendLine("/// ") .Append("/// Executes the command against the connection and converts the results to objects.") .AppendLine("/// ") .AppendLine("/// The for this extension method.") .AppendLine("/// The cancellation instruction.") .AppendLine("/// ") .Append("/// An of objects.") .AppendLine("/// ") .Append("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"") @@ -140,24 +126,18 @@ private static void WriteQueryEntityTask(IndentedStringBuilder codeBuilder, Enti .Append(ThisAssembly.InformationalVersion) .AppendLine("\")]") .Append("public static global::System.Threading.Tasks.Task> QueryAsync(") .IncrementIndent() .AppendLine("this global::FluentCommand.IDataQueryAsync dataQuery,") .AppendLine("global::System.Threading.CancellationToken cancellationToken = default)") .Append("where TEntity : ") - .Append(entity.EntityNamespace) - .Append(".") - .AppendLine(entity.EntityName) + .AppendLine(entity.FullyQualified) .DecrementIndent() .AppendLine("{") .IncrementIndent() .Append("return dataQuery.QueryAsync<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine(">(") .IncrementIndent() .Append("factory: ") @@ -180,17 +160,13 @@ private static void WriteQuerySingleEntity(IndentedStringBuilder codeBuilder, En codeBuilder .AppendLine("/// ") .Append("/// Executes the query and returns the first row in the result as a object.") .AppendLine("/// ") .AppendLine("/// The for this extension method.") .AppendLine("/// ") .Append("/// A instance of if row exists; otherwise null.") .AppendLine("/// ") .Append("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"") @@ -199,23 +175,17 @@ private static void WriteQuerySingleEntity(IndentedStringBuilder codeBuilder, En .Append(ThisAssembly.InformationalVersion) .AppendLine("\")]") .Append("public static ") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine(" QuerySingle(") .IncrementIndent() .AppendLine("this global::FluentCommand.IDataQuery dataQuery)") .Append("where TEntity : ") - .Append(entity.EntityNamespace) - .Append(".") - .AppendLine(entity.EntityName) + .AppendLine(entity.FullyQualified) .DecrementIndent() .AppendLine("{") .IncrementIndent() .Append("return dataQuery.QuerySingle<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine(">(") .IncrementIndent() .Append("factory: ") @@ -238,17 +208,13 @@ private static void WriteQueryEntity(IndentedStringBuilder codeBuilder, EntityCl codeBuilder .AppendLine("/// ") .Append("/// Executes the command against the connection and converts the results to objects.") .AppendLine("/// ") .AppendLine("/// The for this extension method.") .AppendLine("/// ") .Append("/// An of objects.") .AppendLine("/// ") .Append("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"") @@ -257,23 +223,17 @@ private static void WriteQueryEntity(IndentedStringBuilder codeBuilder, EntityCl .Append(ThisAssembly.InformationalVersion) .AppendLine("\")]") .Append("public static global::System.Collections.Generic.IEnumerable<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine("> Query(") .IncrementIndent() .AppendLine("this global::FluentCommand.IDataQuery dataQuery)") .Append("where TEntity : ") - .Append(entity.EntityNamespace) - .Append(".") - .AppendLine(entity.EntityName) + .AppendLine(entity.FullyQualified) .DecrementIndent() .AppendLine("{") .IncrementIndent() .Append("return dataQuery.Query<") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine(">(") .IncrementIndent() .Append("factory: ") @@ -294,17 +254,13 @@ private static void WriteEntityFactory(IndentedStringBuilder codeBuilder, Entity codeBuilder .AppendLine("/// ") .Append("/// A factory for creating objects from the current row in the specified .") .AppendLine("/// ") .AppendLine("/// The open to get the object from.") .AppendLine("/// ") .Append("/// A instance of having property names set that match the field names in the .") .AppendLine("/// ") .Append("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"") @@ -313,9 +269,7 @@ private static void WriteEntityFactory(IndentedStringBuilder codeBuilder, Entity .Append(ThisAssembly.InformationalVersion) .AppendLine("\")]") .Append("public static ") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .Append(" ") .Append(entity.EntityName) .AppendLine("Factory(this global::System.Data.IDataReader dataRecord)") @@ -433,9 +387,7 @@ private static void WriteReturnConstructor(IndentedStringBuilder codeBuilder, En { codeBuilder .Append("return new ") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) + .Append(entity.FullyQualified) .AppendLine("(") .IncrementIndent(); @@ -466,9 +418,7 @@ private static void WriteReturnObjectInitializer(IndentedStringBuilder codeBuild { codeBuilder .Append("return new ") - .Append(entity.EntityNamespace) - .Append(".") - .AppendLine(entity.EntityName) + .AppendLine(entity.FullyQualified) .AppendLine("{") .IncrementIndent(); diff --git a/src/FluentCommand.Generators/GenerateAttributeGenerator.cs b/src/FluentCommand.Generators/GenerateAttributeGenerator.cs new file mode 100644 index 00000000..683d7f72 --- /dev/null +++ b/src/FluentCommand.Generators/GenerateAttributeGenerator.cs @@ -0,0 +1,56 @@ +using FluentCommand.Generators.Models; + +using Microsoft.CodeAnalysis; + +namespace FluentCommand.Generators; + +[Generator(LanguageNames.CSharp)] +public class GenerateAttributeGenerator : DataReaderFactoryGenerator, IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "FluentCommand.Attributes.GenerateReaderAttribute", + predicate: SyntacticPredicate, + transform: SemanticTransform + ) + .Where(static context => context is not null); + + // Emit the diagnostics, if needed + var diagnostics = provider + .Select(static (item, _) => item.Diagnostics) + .Where(static item => item.Count > 0); + + context.RegisterSourceOutput(diagnostics, ReportDiagnostic); + + var entityClasses = provider + .Select(static (item, _) => item.EntityClass) + .Where(static item => item is not null); + + context.RegisterSourceOutput(entityClasses, WriteSource); + } + + private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken) + { + return true; + } + + private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + if (context.Attributes.Length == 0) + return null; + + var attribute = context.Attributes[0]; + if (attribute == null) + return null; + + if (attribute.ConstructorArguments.Length != 1) + return null; + + var comparerArgument = attribute.ConstructorArguments[0]; + if (comparerArgument.Value is not INamedTypeSymbol targetSymbol) + return null; + + return CreateContext(context.TargetNode.GetLocation(), targetSymbol); + } +} diff --git a/src/FluentCommand.Generators/Internal/EquatableArray.cs b/src/FluentCommand.Generators/Internal/EquatableArray.cs deleted file mode 100644 index f1e3a961..00000000 --- a/src/FluentCommand.Generators/Internal/EquatableArray.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Collections; - -namespace FluentCommand.Generators.Internal; - -public readonly struct EquatableArray : IReadOnlyCollection, IEquatable> - where T : IEquatable -{ - public static readonly EquatableArray Empty = new(Array.Empty()); - - private readonly T[] _array; - - public EquatableArray(T[] array) - { - _array = array; - } - - public EquatableArray(IEnumerable array) - { - array ??= Enumerable.Empty(); - _array = array.ToArray(); - } - - public bool Equals(EquatableArray array) - { - return AsSpan().SequenceEqual(array.AsSpan()); - } - - public override bool Equals(object obj) - { - return obj is EquatableArray array && Equals(this, array); - } - - public override int GetHashCode() - { - if (_array is null) - return 0; - - HashCode hashCode = default; - - foreach (T item in _array) - hashCode.Add(item); - - return hashCode.ToHashCode(); - } - - public ReadOnlySpan AsSpan() - { - return _array.AsSpan(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator(); - } - - public int Count => _array?.Length ?? 0; - - public static bool operator ==(EquatableArray left, EquatableArray right) - { - return left.Equals(right); - } - - public static bool operator !=(EquatableArray left, EquatableArray right) - { - return !left.Equals(right); - } -} diff --git a/src/FluentCommand.Generators/Internal/HashCode.cs b/src/FluentCommand.Generators/Internal/HashCode.cs deleted file mode 100644 index f70d006e..00000000 --- a/src/FluentCommand.Generators/Internal/HashCode.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace FluentCommand.Generators.Internal; - -/// -/// Polyfill for .NET 6 HashCode -/// -internal struct HashCode -{ - private static readonly uint s_seed = GenerateGlobalSeed(); - - private const uint Prime1 = 2654435761U; - private const uint Prime2 = 2246822519U; - private const uint Prime3 = 3266489917U; - private const uint Prime4 = 668265263U; - private const uint Prime5 = 374761393U; - - private uint _v1, _v2, _v3, _v4; - private uint _queue1, _queue2, _queue3; - private uint _length; - - private static uint GenerateGlobalSeed() - { - var buffer = new byte[sizeof(uint)]; - new Random().NextBytes(buffer); - return BitConverter.ToUInt32(buffer, 0); - } - - public static int Combine(T1 value1) - { - // Provide a way of diffusing bits from something with a limited - // input hash space. For example, many enums only have a few - // possible hashes, only using the bottom few bits of the code. Some - // collections are built on the assumption that hashes are spread - // over a larger space, so diffusing the bits may help the - // collection work more efficiently. - - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 4; - - hash = QueueRound(hash, hc1); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 8; - - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 12; - - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - hash = QueueRound(hash, hc3); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 16; - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 20; - - hash = QueueRound(hash, hc5); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 24; - - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, - T6 value6, T7 value7) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - uint hc7 = (uint)(value7?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 28; - - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - hash = QueueRound(hash, hc7); - - hash = MixFinal(hash); - return (int)hash; - } - - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, - T6 value6, T7 value7, T8 value8) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - uint hc7 = (uint)(value7?.GetHashCode() ?? 0); - uint hc8 = (uint)(value8?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - v1 = Round(v1, hc5); - v2 = Round(v2, hc6); - v3 = Round(v3, hc7); - v4 = Round(v4, hc8); - - uint hash = MixState(v1, v2, v3, v4); - hash += 32; - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) - { - v1 = s_seed + Prime1 + Prime2; - v2 = s_seed + Prime2; - v3 = s_seed; - v4 = s_seed - Prime1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Round(uint hash, uint input) - { - return RotateLeft(hash + input * Prime2, 13) * Prime1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint QueueRound(uint hash, uint queuedValue) - { - return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixState(uint v1, uint v2, uint v3, uint v4) - { - return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); - } - - private static uint MixEmptyState() - { - return s_seed + Prime5; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixFinal(uint hash) - { - hash ^= hash >> 15; - hash *= Prime2; - hash ^= hash >> 13; - hash *= Prime3; - hash ^= hash >> 16; - return hash; - } - - public void Add(T value) - { - Add(value?.GetHashCode() ?? 0); - } - - public void Add(T value, IEqualityComparer comparer) - { - Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); - } - - private void Add(int value) - { - // The original xxHash works as follows: - // 0. Initialize immediately. We can't do this in a struct (no - // default ctor). - // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. - // 2. Accumulate remaining blocks of length 4 (1 uint) into the - // hash. - // 3. Accumulate remaining blocks of length 1 into the hash. - - // There is no need for #3 as this type only accepts ints. _queue1, - // _queue2 and _queue3 are basically a buffer so that when - // ToHashCode is called we can execute #2 correctly. - - // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see - // #0) nd the last place that can be done if you look at the - // original code is just before the first block of 16 bytes is mixed - // in. The xxHash32 state is never used for streams containing fewer - // than 16 bytes. - - // To see what's really going on here, have a look at the Combine - // methods. - - uint val = (uint)value; - - // Storing the value of _length locally shaves of quite a few bytes - // in the resulting machine code. - uint previousLength = _length++; - uint position = previousLength % 4; - - // Switch can't be inlined. - - if (position == 0) - _queue1 = val; - else if (position == 1) - _queue2 = val; - else if (position == 2) - _queue3 = val; - else // position == 3 - { - if (previousLength == 3) - Initialize(out _v1, out _v2, out _v3, out _v4); - - _v1 = Round(_v1, _queue1); - _v2 = Round(_v2, _queue2); - _v3 = Round(_v3, _queue3); - _v4 = Round(_v4, val); - } - } - - public int ToHashCode() - { - // Storing the value of _length locally shaves of quite a few bytes - // in the resulting machine code. - uint length = _length; - - // position refers to the *next* queue position in this method, so - // position == 1 means that _queue1 is populated; _queue2 would have - // been populated on the next call to Add. - uint position = length % 4; - - // If the length is less than 4, _v1 to _v4 don't contain anything - // yet. xxHash32 treats this differently. - - uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); - - // _length is incremented once per Add(Int32) and is therefore 4 - // times too small (xxHash length is in bytes, not ints). - - hash += length * 4; - - // Mix what remains in the queue - - // Switch can't be inlined right now, so use as few branches as - // possible by manually excluding impossible scenarios (position > 1 - // is always false if position is not > 0). - if (position > 0) - { - hash = QueueRound(hash, _queue1); - if (position > 1) - { - hash = QueueRound(hash, _queue2); - if (position > 2) - hash = QueueRound(hash, _queue3); - } - } - - hash = MixFinal(hash); - return (int)hash; - } - -#pragma warning disable 0809 - // Obsolete member 'memberA' overrides non-obsolete member 'memberB'. - // Disallowing GetHashCode and Equals is by design - - // * We decided to not override GetHashCode() to produce the hash code - // as this would be weird, both naming-wise as well as from a - // behavioral standpoint (GetHashCode() should return the object's - // hash code, not the one being computed). - - // * Even though ToHashCode() can be called safely multiple times on - // this implementation, it is not part of the contract. If the - // implementation has to change in the future we don't want to worry - // about people who might have incorrectly used this type. - - [Obsolete( - "HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", - error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => throw new NotSupportedException("Hash code not supported"); - - [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => throw new NotSupportedException("Equality not supported"); -#pragma warning restore 0809 - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint RotateLeft(uint value, int offset) - => (value << offset) | (value >> (32 - offset)); -} diff --git a/src/FluentCommand.Generators/Internal/IsExternalInit.cs b/src/FluentCommand.Generators/Internal/IsExternalInit.cs deleted file mode 100644 index 40f1f7b0..00000000 --- a/src/FluentCommand.Generators/Internal/IsExternalInit.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.ComponentModel; - -// ReSharper disable once CheckNamespace -namespace System.Runtime.CompilerServices; - -/// -/// Reserved to be used by the compiler for tracking metadata. -/// This class should not be used by developers in source code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit -{ -} diff --git a/src/FluentCommand.Generators/IsExternalInit.cs b/src/FluentCommand.Generators/IsExternalInit.cs new file mode 100644 index 00000000..75cd67bb --- /dev/null +++ b/src/FluentCommand.Generators/IsExternalInit.cs @@ -0,0 +1,6 @@ +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit; diff --git a/src/FluentCommand.Generators/Models/EntityClass.cs b/src/FluentCommand.Generators/Models/EntityClass.cs index ca39370a..4c213666 100644 --- a/src/FluentCommand.Generators/Models/EntityClass.cs +++ b/src/FluentCommand.Generators/Models/EntityClass.cs @@ -1,59 +1,9 @@ -using FluentCommand.Generators.Internal; - namespace FluentCommand.Generators.Models; -public sealed class EntityClass : IEquatable -{ - public EntityClass( - InitializationMode initializationMode, - string entityNamespace, - string entityName, - IEnumerable properties) - { - InitializationMode = initializationMode; - EntityNamespace = entityNamespace; - EntityName = entityName; - Properties = new EquatableArray(properties); - } - - public InitializationMode InitializationMode { get; } - - public string EntityNamespace { get; } - - public string EntityName { get; } - - public EquatableArray Properties { get; } - - public bool Equals(EntityClass other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return InitializationMode == other.InitializationMode - && EntityNamespace == other.EntityNamespace - && EntityName == other.EntityName - && Properties.Equals(other.Properties); - } - - public override bool Equals(object obj) - { - return ReferenceEquals(this, obj) || obj is EntityClass other && Equals(other); - } - - public override int GetHashCode() - { - return HashCode.Combine(InitializationMode, EntityNamespace, EntityName, Properties); - } - - public static bool operator ==(EntityClass left, EntityClass right) - { - return Equals(left, right); - } - - public static bool operator !=(EntityClass left, EntityClass right) - { - return !Equals(left, right); - } -} +public record EntityClass( + InitializationMode InitializationMode, + string FullyQualified, + string EntityNamespace, + string EntityName, + EquatableArray Properties +); diff --git a/src/FluentCommand.Generators/Models/EntityContext.cs b/src/FluentCommand.Generators/Models/EntityContext.cs index 97518029..24c5a880 100644 --- a/src/FluentCommand.Generators/Models/EntityContext.cs +++ b/src/FluentCommand.Generators/Models/EntityContext.cs @@ -1,52 +1,8 @@ -using FluentCommand.Generators.Internal; - using Microsoft.CodeAnalysis; namespace FluentCommand.Generators.Models; -public class EntityContext : IEquatable -{ - public EntityContext( - EntityClass entityClass, - IEnumerable diagnostics) - { - EntityClass = entityClass; - Diagnostics = new EquatableArray(diagnostics); - } - - public EntityClass EntityClass { get; } - - public EquatableArray Diagnostics { get; } - - public bool Equals(EntityContext other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - return Equals(EntityClass, other.EntityClass) - && Diagnostics.Equals(other.Diagnostics); - } - - public override bool Equals(object obj) - { - return obj is EntityContext context && Equals(context); - } - - public override int GetHashCode() - { - return HashCode.Combine(EntityClass, Diagnostics); - } - - public static bool operator ==(EntityContext left, EntityContext right) - { - return Equals(left, right); - } - - public static bool operator !=(EntityContext left, EntityContext right) - { - return !Equals(left, right); - } -} +public record EntityContext( + EntityClass EntityClass, + EquatableArray Diagnostics +); diff --git a/src/FluentCommand.Generators/Models/EntityProperty.cs b/src/FluentCommand.Generators/Models/EntityProperty.cs index 82073c45..2860c314 100644 --- a/src/FluentCommand.Generators/Models/EntityProperty.cs +++ b/src/FluentCommand.Generators/Models/EntityProperty.cs @@ -1,64 +1,9 @@ -using FluentCommand.Generators.Internal; - namespace FluentCommand.Generators.Models; -public sealed class EntityProperty : IEquatable -{ - public EntityProperty( - string propertyName, - string columnName, - string propertyType, - string parameterName = null, - string converterName = null) - { - PropertyName = propertyName; - ColumnName = columnName; - PropertyType = propertyType; - ParameterName = parameterName; - ConverterName = converterName; - } - - public string PropertyName { get; } - - public string ColumnName { get; } - - public string PropertyType { get; } - - public string ParameterName { get; } - - public string ConverterName { get; } - - public bool Equals(EntityProperty other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - return PropertyName == other.PropertyName - && PropertyType == other.PropertyType - && ParameterName == other.ParameterName - && ConverterName == other.ConverterName; - } - - public override bool Equals(object obj) - { - return obj is EntityProperty property && Equals(property); - } - - public override int GetHashCode() - { - return HashCode.Combine(PropertyName, PropertyType, ParameterName, ConverterName); - } - - public static bool operator ==(EntityProperty left, EntityProperty right) - { - return Equals(left, right); - } - - public static bool operator !=(EntityProperty left, EntityProperty right) - { - return !Equals(left, right); - } -} +public record EntityProperty( + string PropertyName, + string ColumnName, + string PropertyType, + string ParameterName = null, + string ConverterName = null +); diff --git a/src/FluentCommand.Generators/Models/EquatableArray.cs b/src/FluentCommand.Generators/Models/EquatableArray.cs new file mode 100644 index 00000000..56d90651 --- /dev/null +++ b/src/FluentCommand.Generators/Models/EquatableArray.cs @@ -0,0 +1,62 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +#nullable enable + +namespace FluentCommand.Generators.Models; + +[ExcludeFromCodeCoverage] +public readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + public static readonly EquatableArray Empty = new(); + + + public EquatableArray() : this([]) { } + + public EquatableArray(T[] array) => Array = array ?? []; + + public EquatableArray(IEnumerable items) => Array = items.ToArray() ?? []; + + + public T[] Array { get; } + + public int Count => Array.Length; + + + public ReadOnlySpan AsSpan() => Array.AsSpan(); + + public T[] AsArray() => Array; + + + public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right); + + public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right); + + public bool Equals(EquatableArray array) => Array.AsSpan().SequenceEqual(array.AsSpan()); + + public override bool Equals(object? obj) => obj is EquatableArray array && Equals(this, array); + + public override int GetHashCode() + { + if (Array is not T[] array) + return 0; + + var hashCode = 16777619; + + for (int i = 0; i < array.Length; i++) + hashCode = unchecked((hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(array[i])); + + return hashCode; + } + + + IEnumerator IEnumerable.GetEnumerator() => (Array as IEnumerable).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => Array.GetEnumerator(); + + + public static implicit operator EquatableArray(T[] array) => new(array); + + public static implicit operator EquatableArray(List items) => new(items); +} diff --git a/src/FluentCommand.Generators/TableAttributeGenerator.cs b/src/FluentCommand.Generators/TableAttributeGenerator.cs new file mode 100644 index 00000000..68d6a4b2 --- /dev/null +++ b/src/FluentCommand.Generators/TableAttributeGenerator.cs @@ -0,0 +1,54 @@ +using FluentCommand.Generators.Models; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace FluentCommand.Generators; + +[Generator(LanguageNames.CSharp)] +public class TableAttributeGenerator : DataReaderFactoryGenerator, IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "System.ComponentModel.DataAnnotations.Schema.TableAttribute", + predicate: SyntacticPredicate, + transform: SemanticTransform + ) + .Where(static context => context is not null); + + // Emit the diagnostics, if needed + var diagnostics = provider + .Select(static (item, _) => item.Diagnostics) + .Where(static item => item.Count > 0); + + context.RegisterSourceOutput(diagnostics, ReportDiagnostic); + + var entityClasses = provider + .Select(static (item, _) => item.EntityClass) + .Where(static item => item is not null); + + context.RegisterSourceOutput(entityClasses, WriteSource); + } + + private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken) + { + return (syntaxNode is ClassDeclarationSyntax + { AttributeLists.Count: > 0 } classDeclaration + && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword) + && !classDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)) + || (syntaxNode is RecordDeclarationSyntax + { AttributeLists.Count: > 0 } recordDeclaration + && !recordDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword) + && !recordDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)); + } + + private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + if (context.TargetSymbol is not INamedTypeSymbol targetSymbol) + return null; + + return CreateContext(context.TargetNode.GetLocation(), targetSymbol); + } +} diff --git a/src/FluentCommand/Attributes/GenerateReaderAttribute.cs b/src/FluentCommand/Attributes/GenerateReaderAttribute.cs new file mode 100644 index 00000000..cd3cbc97 --- /dev/null +++ b/src/FluentCommand/Attributes/GenerateReaderAttribute.cs @@ -0,0 +1,14 @@ +using System.Diagnostics; + +namespace FluentCommand.Attributes; + +/// +/// Source generate a data reader for the specified type +/// +/// The type to generate a data reader for +[Conditional("FLUENTCOMMAND_GENERATOR")] +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Module, AllowMultiple = true)] +public class GenerateReaderAttribute(Type type) : Attribute +{ + public Type Type { get; } = type ?? throw new ArgumentNullException(nameof(type)); +} diff --git a/test/FluentCommand.Entities/Brand.cs b/test/FluentCommand.Entities/Brand.cs new file mode 100644 index 00000000..af7411d6 --- /dev/null +++ b/test/FluentCommand.Entities/Brand.cs @@ -0,0 +1,13 @@ +using FluentCommand.Attributes; +using FluentCommand.Entities; + +[assembly: GenerateReader(typeof(Brand))] + +namespace FluentCommand.Entities; + +public class Brand +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } +} diff --git a/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs b/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs index d1e2525a..5e5ead9b 100644 --- a/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs +++ b/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs @@ -11,6 +11,7 @@ public async Task Generate() { var entityClass = new EntityClass( InitializationMode.ObjectInitializer, + "global::FluentCommand.Entities.Status", "FluentCommand.Entities", "Status", new EntityProperty[] @@ -20,7 +21,7 @@ public async Task Generate() new("IsActive", "IsActive", typeof(bool).FullName), new("Updated", "Updated", typeof(DateTimeOffset).FullName), new("RowVersion", "RowVersion", typeof(byte[]).FullName), - }.ToImmutableArray() + } ); var source = DataReaderFactoryWriter.Generate(entityClass); diff --git a/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt b/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt index 2c7afb2d..71388d81 100644 --- a/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt +++ b/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt @@ -11,34 +11,34 @@ namespace FluentCommand.Entities public static partial class StatusDataReaderExtensions { /// - /// Executes the command against the connection and converts the results to objects. + /// Executes the command against the connection and converts the results to objects. /// /// The for this extension method. /// - /// An of objects. + /// An of objects. /// - public static global::System.Collections.Generic.IEnumerable Query( + public static global::System.Collections.Generic.IEnumerable Query( this global::FluentCommand.IDataQuery dataQuery) - where TEntity : FluentCommand.Entities.Status + where TEntity : global::FluentCommand.Entities.Status { - return dataQuery.Query( + return dataQuery.Query( factory: StatusDataReaderExtensions.StatusFactory, commandBehavior: global::System.Data.CommandBehavior.SequentialAccess | global::System.Data.CommandBehavior.SingleResult); } /// - /// Executes the query and returns the first row in the result as a object. + /// Executes the query and returns the first row in the result as a object. /// /// The for this extension method. /// - /// A instance of if row exists; otherwise null. + /// A instance of if row exists; otherwise null. /// - public static FluentCommand.Entities.Status QuerySingle( + public static global::FluentCommand.Entities.Status QuerySingle( this global::FluentCommand.IDataQuery dataQuery) - where TEntity : FluentCommand.Entities.Status + where TEntity : global::FluentCommand.Entities.Status { - return dataQuery.QuerySingle( + return dataQuery.QuerySingle( factory: StatusDataReaderExtensions.StatusFactory, commandBehavior: global::System.Data.CommandBehavior.SequentialAccess | global::System.Data.CommandBehavior.SingleResult | @@ -46,19 +46,19 @@ namespace FluentCommand.Entities } /// - /// Executes the command against the connection and converts the results to objects. + /// Executes the command against the connection and converts the results to objects. /// /// The for this extension method. /// The cancellation instruction. /// - /// An of objects. + /// An of objects. /// - public static global::System.Threading.Tasks.Task> QueryAsync( + public static global::System.Threading.Tasks.Task> QueryAsync( this global::FluentCommand.IDataQueryAsync dataQuery, global::System.Threading.CancellationToken cancellationToken = default) - where TEntity : FluentCommand.Entities.Status + where TEntity : global::FluentCommand.Entities.Status { - return dataQuery.QueryAsync( + return dataQuery.QueryAsync( factory: StatusDataReaderExtensions.StatusFactory, commandBehavior: global::System.Data.CommandBehavior.SequentialAccess | global::System.Data.CommandBehavior.SingleResult, @@ -66,19 +66,19 @@ namespace FluentCommand.Entities } /// - /// Executes the query and returns the first row in the result as a object. + /// Executes the query and returns the first row in the result as a object. /// /// The for this extension method. /// The cancellation instruction. /// - /// A instance of if row exists; otherwise null. + /// A instance of if row exists; otherwise null. /// - public static global::System.Threading.Tasks.Task QuerySingleAsync( + public static global::System.Threading.Tasks.Task QuerySingleAsync( this global::FluentCommand.IDataQueryAsync dataQuery, global::System.Threading.CancellationToken cancellationToken = default) - where TEntity : FluentCommand.Entities.Status + where TEntity : global::FluentCommand.Entities.Status { - return dataQuery.QuerySingleAsync( + return dataQuery.QuerySingleAsync( factory: StatusDataReaderExtensions.StatusFactory, commandBehavior: global::System.Data.CommandBehavior.SequentialAccess | global::System.Data.CommandBehavior.SingleResult | @@ -87,13 +87,13 @@ namespace FluentCommand.Entities } /// - /// A factory for creating objects from the current row in the specified . + /// A factory for creating objects from the current row in the specified . /// /// The open to get the object from. /// - /// A instance of having property names set that match the field names in the . + /// A instance of having property names set that match the field names in the . /// - public static FluentCommand.Entities.Status StatusFactory(this global::System.Data.IDataReader dataRecord) + public static global::FluentCommand.Entities.Status StatusFactory(this global::System.Data.IDataReader dataRecord) { if (dataRecord == null) throw new global::System.ArgumentNullException(nameof(dataRecord)); @@ -130,7 +130,7 @@ namespace FluentCommand.Entities } } - return new FluentCommand.Entities.Status + return new global::FluentCommand.Entities.Status { Id = v_id, Name = v_name,