From 3751501470442fc5cb40c8458b5f048fbbf5a8a7 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 13 Nov 2023 01:38:56 +0100 Subject: [PATCH 01/12] implement model --- .../Internal/Inspection.cs | 79 ++++++++++++++++++- src/Dapper.AOT.Analyzers/Internal/Types.cs | 2 + src/Dapper.AOT/UseColumnAttributeAttribute.cs | 15 ++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/Dapper.AOT/UseColumnAttributeAttribute.cs diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index cd61ee07..d0c1a110 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -369,8 +369,9 @@ public readonly struct ElementMember var loc = GetLocation(attributeName); return loc is null ? null : [loc]; } - + private readonly AttributeData? _dbValue; + public readonly ColumnAttributeData ColumnAttributeData { get; } = ColumnAttributeData.Default; public string DbName => TryGetAttributeValue(_dbValue, "Name", out string? name) && !string.IsNullOrWhiteSpace(name) ? name!.Trim() : CodeName; public string CodeName => Member.Name; @@ -443,6 +444,7 @@ public enum ElementMemberFlags public ElementMember( ISymbol member, AttributeData? dbValue, + ColumnAttributeData columnAttributeData, ElementMemberKind kind, ElementMemberFlags flags, int? constructorParameterOrder, @@ -450,6 +452,7 @@ public ElementMember( { _dbValue = dbValue; _flags = flags; + ColumnAttributeData = columnAttributeData; Member = member; Kind = kind; @@ -483,6 +486,35 @@ public override bool Equals(object obj) => obj is ElementMember other } } + internal struct ColumnAttributeData + { + public UseColumnAttributeState UseColumnAttribute { get; } + public ColumnAttributeState ColumnAttribute { get; } + public string? Name { get; } + + public ColumnAttributeData(UseColumnAttributeState useColumnAttribute, ColumnAttributeState columnAttribute, string? name) + { + UseColumnAttribute = useColumnAttribute; + ColumnAttribute = columnAttribute; + Name = name; + } + + public static ColumnAttributeData Default => new(UseColumnAttributeState.NotSpecified, ColumnAttributeState.NotSpecified, null); + + public enum UseColumnAttributeState + { + NotSpecified, + Disabled, + Enabled + } + + public enum ColumnAttributeState + { + NotSpecified, + Specified + } + } + public enum ConstructorResult { NoneFound, @@ -730,12 +762,55 @@ internal static ImmutableArray GetMembers(bool forParameters, ITy flags |= ElementMember.ElementMemberFlags.IsExpandable; } + var columnAttributeData = ParseColumnAttributeData(member); + // all good, then! - builder.Add(new(member, dbValue, kind, flags, constructorParameterOrder, factoryMethodParamOrder)); + builder.Add(new(member, dbValue, columnAttributeData, kind, flags, constructorParameterOrder, factoryMethodParamOrder)); } return builder.ToImmutable(); } + static ColumnAttributeData ParseColumnAttributeData(ISymbol? member) + { + if (member is null) return ColumnAttributeData.Default; + var useColumnAttributeState = ParseUseColumnAttributeState(); + var (columnAttributeState, columnName) = ParseColumnAttributeState(); + + return new ColumnAttributeData(useColumnAttributeState, columnAttributeState, columnName); + + (ColumnAttributeData.ColumnAttributeState state, string? columnName) ParseColumnAttributeState() + { + var columnAttribute = GetDapperAttribute(member, Types.ColumnAttribute); + if (columnAttribute is null) return (ColumnAttributeData.ColumnAttributeState.NotSpecified, null); + + if (TryGetAttributeValue(columnAttribute, "name", out string? name) && !string.IsNullOrWhiteSpace(name)) + { + return (ColumnAttributeData.ColumnAttributeState.Specified, name); + } + else + { + // [Column] is specified, but value is null. Can happen for ctor overload: + // https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute.-ctor?view=net-7.0#system-componentmodel-dataannotations-schema-columnattribute-ctor + return (ColumnAttributeData.ColumnAttributeState.Specified, null); + } + } + ColumnAttributeData.UseColumnAttributeState ParseUseColumnAttributeState() + { + var useColumnAttribute = GetDapperAttribute(member, Types.UseColumnAttributeAttribute); + if (useColumnAttribute is null) return ColumnAttributeData.UseColumnAttributeState.NotSpecified; + + if (TryGetAttributeValue(useColumnAttribute, "enable", out bool useColumnAttributeState)) + { + return useColumnAttributeState ? ColumnAttributeData.UseColumnAttributeState.Enabled : ColumnAttributeData.UseColumnAttributeState.Disabled; + } + else + { + // default + return ColumnAttributeData.UseColumnAttributeState.Enabled; + } + } + } + static IReadOnlyDictionary ParseMethodParameters(IMethodSymbol constructorSymbol) { var parameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); diff --git a/src/Dapper.AOT.Analyzers/Internal/Types.cs b/src/Dapper.AOT.Analyzers/Internal/Types.cs index 2fe64963..3caa0c45 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Types.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Types.cs @@ -6,6 +6,8 @@ public const string BindTupleByNameAttribute = nameof(BindTupleByNameAttribute), DapperAotAttribute = nameof(DapperAotAttribute), DbValueAttribute = nameof(DbValueAttribute), + ColumnAttribute = nameof(ColumnAttribute), + UseColumnAttributeAttribute = nameof(UseColumnAttributeAttribute), RowCountAttribute = nameof(RowCountAttribute), CacheCommandAttribute = nameof(CacheCommandAttribute), IncludeLocationAttribute = nameof(IncludeLocationAttribute), diff --git a/src/Dapper.AOT/UseColumnAttributeAttribute.cs b/src/Dapper.AOT/UseColumnAttributeAttribute.cs new file mode 100644 index 00000000..1d2d73ad --- /dev/null +++ b/src/Dapper.AOT/UseColumnAttributeAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Dapper +{ + /// + /// Specifies whether to use [System.ComponentModel.DataAnnotations.Schema.ColumnAttribute] for additional behavioral configuration + /// + public class UseColumnAttributeAttribute : Attribute + { + /// enable usage of [Column] attribute + public UseColumnAttributeAttribute(bool enable = true) + { + } + } +} From 2b5dd69d46f929cea15feada412588fe66009eea Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Thu, 16 Nov 2023 21:02:34 +0100 Subject: [PATCH 02/12] merge main --- .../Internal/Inspection.cs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index ea5f5f1b..94db216b 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -398,6 +398,7 @@ public readonly struct ElementMember } private readonly AttributeData? _dbValue; + public readonly ColumnAttributeData ColumnAttributeData { get; } = ColumnAttributeData.Default; public string DbName => TryGetAttributeValue(_dbValue, "Name", out string? name) && !string.IsNullOrWhiteSpace(name) ? name!.Trim() : CodeName; public string CodeName => Member.Name; @@ -472,6 +473,7 @@ public enum ElementMemberFlags public ElementMember( ISymbol member, AttributeData? dbValue, + ColumnAttributeData columnAttributeData, ElementMemberKind kind, ElementMemberFlags flags, int? constructorParameterOrder, @@ -479,6 +481,7 @@ public ElementMember( { _dbValue = dbValue; _flags = flags; + ColumnAttributeData = columnAttributeData; Member = member; Kind = kind; @@ -512,6 +515,35 @@ public override bool Equals(object obj) => obj is ElementMember other } } + internal struct ColumnAttributeData + { + public UseColumnAttributeState UseColumnAttribute { get; } + public ColumnAttributeState ColumnAttribute { get; } + public string? Name { get; } + + public ColumnAttributeData(UseColumnAttributeState useColumnAttribute, ColumnAttributeState columnAttribute, string? name) + { + UseColumnAttribute = useColumnAttribute; + ColumnAttribute = columnAttribute; + Name = name; + } + + public static ColumnAttributeData Default => new(UseColumnAttributeState.NotSpecified, ColumnAttributeState.NotSpecified, null); + + public enum UseColumnAttributeState + { + NotSpecified, + Disabled, + Enabled + } + + public enum ColumnAttributeState + { + NotSpecified, + Specified + } + } + public enum ConstructorResult { NoneFound, @@ -761,12 +793,55 @@ internal static ImmutableArray GetMembers(bool forParameters, ITy flags |= ElementMember.ElementMemberFlags.IsExpandable; } + var columnAttributeData = ParseColumnAttributeData(member); + // all good, then! - builder.Add(new(member, dbValue, kind, flags, constructorParameterOrder, factoryMethodParamOrder)); + builder.Add(new(member, dbValue, columnAttributeData, kind, flags, constructorParameterOrder, factoryMethodParamOrder)); } return builder.ToImmutable(); } + static ColumnAttributeData ParseColumnAttributeData(ISymbol? member) + { + if (member is null) return ColumnAttributeData.Default; + var useColumnAttributeState = ParseUseColumnAttributeState(); + var (columnAttributeState, columnName) = ParseColumnAttributeState(); + + return new ColumnAttributeData(useColumnAttributeState, columnAttributeState, columnName); + + (ColumnAttributeData.ColumnAttributeState state, string? columnName) ParseColumnAttributeState() + { + var columnAttribute = GetDapperAttribute(member, Types.ColumnAttribute); + if (columnAttribute is null) return (ColumnAttributeData.ColumnAttributeState.NotSpecified, null); + + if (TryGetAttributeValue(columnAttribute, "name", out string? name) && !string.IsNullOrWhiteSpace(name)) + { + return (ColumnAttributeData.ColumnAttributeState.Specified, name); + } + else + { + // [Column] is specified, but value is null. Can happen for ctor overload: + // https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute.-ctor?view=net-7.0#system-componentmodel-dataannotations-schema-columnattribute-ctor + return (ColumnAttributeData.ColumnAttributeState.Specified, null); + } + } + ColumnAttributeData.UseColumnAttributeState ParseUseColumnAttributeState() + { + var useColumnAttribute = GetDapperAttribute(member, Types.UseColumnAttributeAttribute); + if (useColumnAttribute is null) return ColumnAttributeData.UseColumnAttributeState.NotSpecified; + + if (TryGetAttributeValue(useColumnAttribute, "enable", out bool useColumnAttributeState)) + { + return useColumnAttributeState ? ColumnAttributeData.UseColumnAttributeState.Enabled : ColumnAttributeData.UseColumnAttributeState.Disabled; + } + else + { + // default + return ColumnAttributeData.UseColumnAttributeState.Enabled; + } + } + } + static IReadOnlyDictionary ParseMethodParameters(IMethodSymbol constructorSymbol) { var parameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); From b49fe1038cfde1477694068f44ef54a9bb50089c Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sat, 18 Nov 2023 01:13:39 +0100 Subject: [PATCH 03/12] support [column] attribute --- Directory.Packages.props | 75 +- docs/rules/DAP042.md | 41 + docs/rules/DAP043.md | 28 + .../DapperAnalyzer.Diagnostics.cs | 2 + .../CodeAnalysis/DapperAnalyzer.cs | 30 + .../Internal/Inspection.cs | 62 +- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 1 + .../Interceptors/ColumnAttribute.input.cs | 41 + .../Interceptors/ColumnAttribute.output.cs | 142 +++ .../Interceptors/ColumnAttribute.output.txt | 4 + .../QueryCustomConstruction.output.netfx.cs | 1013 ----------------- .../QueryCustomConstruction.output.netfx.txt | 4 - .../TestCommon/RoslynTestHelpers.cs | 1 + test/Dapper.AOT.Test/Verifiers/DAP042.cs | 38 + test/Dapper.AOT.Test/Verifiers/DAP043.cs | 36 + test/Dapper.AOT.Test/Verifiers/Verifier.cs | 1 + 16 files changed, 455 insertions(+), 1064 deletions(-) create mode 100644 docs/rules/DAP042.md create mode 100644 docs/rules/DAP043.md create mode 100644 test/Dapper.AOT.Test/Interceptors/ColumnAttribute.input.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.txt delete mode 100644 test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.cs delete mode 100644 test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.txt create mode 100644 test/Dapper.AOT.Test/Verifiers/DAP042.cs create mode 100644 test/Dapper.AOT.Test/Verifiers/DAP043.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index e2940d07..7e47942f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,39 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/rules/DAP042.md b/docs/rules/DAP042.md new file mode 100644 index 00000000..0b59ece8 --- /dev/null +++ b/docs/rules/DAP042.md @@ -0,0 +1,41 @@ +# DAP042 + +Usage of [[Column]](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) +attribute and `[DbValue]` attributes are conflicting - Dapper will choose either one or another's name override. + +This conflict can lead to unexpected behavior, so we recommend to use only one of them. + +Bad: + +``` csharp +class MyType +{ + [DbValue(Name = "MyOtherOtherName")] + [UseColumnAttribute] + [Column("MyOtherName")] + public int MyThisName { get; set; } +} +``` + +Good: + +``` csharp +class MyType +{ + [DbValue(Name = "MyOtherOtherName")] + // without [Column] and [UseColumnAttribute] + public int MyThisName { get; set; } +} +``` + +Another good: + +``` csharp +class MyType +{ + // without [DbValue] + [UseColumnAttribute] + [Column("MyOtherName")] + public int MyThisName { get; set; } +} +``` \ No newline at end of file diff --git a/docs/rules/DAP043.md b/docs/rules/DAP043.md new file mode 100644 index 00000000..e769f4ec --- /dev/null +++ b/docs/rules/DAP043.md @@ -0,0 +1,28 @@ +# DAP043 + +If you are looking at `DAP043`, then most probably you wanted to use [[Column]](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) +attribute to override the name of the type member. + +However, due to [historical reasons](https://stackoverflow.com/a/77073456) you need to add a "marker attribute" `[UseColumnAttribute]` +to explicitly let Dapper know you want to override the type member's name. + +Bad: + +``` csharp +class MyType +{ + [Column("MyOtherName")] + public int MyThisName { get; set; } +} +``` + +Good: + +``` csharp +class MyType +{ + [UseColumnAttribute] + [Column("MyOtherName")] + public int MyThisName { get; set; } +} +``` \ No newline at end of file diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs index c56a44e2..bb4ff422 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs @@ -49,6 +49,8 @@ public static readonly DiagnosticDescriptor ValueTypeSingleFirstOrDefaultUsage = LibraryWarning("DAP038", "Value-type single row 'OrDefault' usage", "Type '{0}' is a value-type; it will not be trivial to identify missing rows from {1}"), FactoryMethodMultipleExplicit = LibraryError("DAP039", "Multiple explicit factory methods", "Only one factory method should be marked [ExplicitConstructor] for type '{0}'"), ConstructorOverridesFactoryMethod = LibraryWarning("DAP041", "Constructor overrides factory method", "Type '{0}' has both constructor and factory method; Constructor will be used instead of a factory method"), + ParameterNameOverrideConflict = LibraryWarning("DAP042", "Parameter name override conflict", "A column name is specified via both [DbValue] and [Column]; '{0}' will be used"), + UseColumnAttributeNotSpecified = LibraryWarning("DAP043", "[Column] has no effect", "Attach the [UseColumnAttribute] attribute to make Dapper consider [Column]"), // SQL parse specific GeneralSqlError = SqlWarning("DAP200", "SQL error", "SQL error: {0}"), diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index da9719ef..80c8a0e4 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -197,6 +197,14 @@ private void ValidateDapperMethod(in OperationAnalysisContext ctx, IOperation sq var resultMap = MemberMap.CreateForResults(resultType, location); if (resultMap is not null) { + if (resultMap.Members.Length != 0) + { + foreach (var member in resultMap.Members) + { + ValidateMember(member, onDiagnostic); + } + } + // check for single-row value-type usage if (flags.HasAny(OperationFlags.SingleRow) && !flags.HasAny(OperationFlags.AtLeastOne) && resultMap.ElementType.IsValueType && !CouldBeNullable(resultMap.ElementType)) @@ -817,6 +825,28 @@ enum ParameterMode ? null : new(rowCountHint, rowCountHintMember?.Member.Name, batchSize, cmdProps); } + static void ValidateMember(ElementMember member, Action? reportDiagnostic) + { + ValidateColumnAttribute(); + + void ValidateColumnAttribute() + { + if (member.HasDbValueAttribute && member.ColumnAttributeData.IsCorrectUsage) + { + reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.ParameterNameOverrideConflict, + location: member.GetLocation(Types.DbValueAttribute), + additionalLocations: member.AsAdditionalLocations(Types.ColumnAttribute, allowNonDapperLocations: true), + messageArgs: member.DbName)); + } + if (member.ColumnAttributeData.ColumnAttribute == ColumnAttributeData.ColumnAttributeState.Specified + && member.ColumnAttributeData.UseColumnAttribute == ColumnAttributeData.UseColumnAttributeState.NotSpecified) + { + reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.UseColumnAttributeNotSpecified, + location: member.GetLocation(Types.ColumnAttribute, allowNonDapperLocations: true))); + } + } + } + internal static ImmutableArray? SharedGetParametersToInclude(MemberMap? map, ref OperationFlags flags, string? sql, Action? reportDiagnostic, out SqlParseOutputFlags parseFlags) { SortedDictionary? byDbName = null; diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index 94db216b..c7cb4e06 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -142,6 +142,22 @@ public static bool IsDapperAttribute(AttributeData attrib) } }; + public static AttributeData? GetAttribute(ISymbol? symbol, string attributeName) + { + if (symbol is not null) + { + foreach (var attrib in symbol.GetAttributes()) + { + if (attrib.AttributeClass!.Name == attributeName) + { + return attrib; + } + } + } + + return null; + } + public static AttributeData? GetDapperAttribute(ISymbol? symbol, string attributeName) { if (symbol is not null) @@ -391,16 +407,33 @@ public enum ElementMemberKind } public readonly struct ElementMember { - public Location[]? AsAdditionalLocations(string? attributeName = null) + public Location[]? AsAdditionalLocations(string? attributeName = null, bool allowNonDapperLocations = false) { - var loc = GetLocation(attributeName); + var loc = GetLocation(attributeName, allowNonDapperLocations); return loc is null ? null : [loc]; } private readonly AttributeData? _dbValue; public readonly ColumnAttributeData ColumnAttributeData { get; } = ColumnAttributeData.Default; - public string DbName => TryGetAttributeValue(_dbValue, "Name", out string? name) - && !string.IsNullOrWhiteSpace(name) ? name!.Trim() : CodeName; + public string DbName + { + get + { + if (TryGetAttributeValue(_dbValue, "Name", out string? name) && !string.IsNullOrWhiteSpace(name)) + { + // priority 1: [DbValue] attribute + return name!.Trim(); + } + + if (ColumnAttributeData.IsCorrectUsage) + { + // priority 2: [Column] attribute + return ColumnAttributeData.Name!; + } + + return CodeName; + } + } public string CodeName => Member.Name; public ISymbol Member { get; } public ITypeSymbol CodeType => Member switch @@ -507,11 +540,16 @@ public override bool Equals(object obj) => obj is ElementMember other } return null; } - public Location? GetLocation(string? attributeName = null) + public Location? GetLocation(string? attributeName = null, bool allowNonDapperLocations = false) { - var loc = attributeName is null ? null : - GetDapperAttribute(Member, attributeName)?.ApplicationSyntaxReference?.GetSyntax()?.GetLocation(); - return loc ?? GetLocation(); + if (attributeName is null) + { + return GetLocation(); + } + + return allowNonDapperLocations + ? GetAttribute(Member, attributeName)?.ApplicationSyntaxReference?.GetSyntax()?.GetLocation() + : GetDapperAttribute(Member, attributeName)?.ApplicationSyntaxReference?.GetSyntax()?.GetLocation(); } } @@ -528,6 +566,10 @@ public ColumnAttributeData(UseColumnAttributeState useColumnAttribute, ColumnAtt Name = name; } + public bool IsCorrectUsage + => UseColumnAttribute is UseColumnAttributeState.NotSpecified or UseColumnAttributeState.Enabled + && ColumnAttribute is ColumnAttributeState.Specified && !string.IsNullOrEmpty(Name); + public static ColumnAttributeData Default => new(UseColumnAttributeState.NotSpecified, ColumnAttributeState.NotSpecified, null); public enum UseColumnAttributeState @@ -811,7 +853,7 @@ static ColumnAttributeData ParseColumnAttributeData(ISymbol? member) (ColumnAttributeData.ColumnAttributeState state, string? columnName) ParseColumnAttributeState() { - var columnAttribute = GetDapperAttribute(member, Types.ColumnAttribute); + var columnAttribute = GetAttribute(member, Types.ColumnAttribute); if (columnAttribute is null) return (ColumnAttributeData.ColumnAttributeState.NotSpecified, null); if (TryGetAttributeValue(columnAttribute, "name", out string? name) && !string.IsNullOrWhiteSpace(name)) @@ -875,7 +917,7 @@ private static bool TryGetAttributeValue(AttributeData? attrib, string name, int index = 0; foreach (var p in ctor.Parameters) { - if (StringComparer.InvariantCultureIgnoreCase.Equals(p.Name == name)) + if (p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) { value = Parse(attrib.ConstructorArguments[index], out isNull); return true; diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 68d37e5d..13a9e7b7 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -35,6 +35,7 @@ + diff --git a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.input.cs b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.input.cs new file mode 100644 index 00000000..76ff8d61 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.input.cs @@ -0,0 +1,41 @@ +using Dapper; +using System.Data.Common; + +// needed for [Column] attribute +using System.ComponentModel.DataAnnotations.Schema; + +[module: DapperAot] + +public static class Foo +{ + static void SomeCode(DbConnection connection, string bar, bool isBuffered) + { + _ = connection.Query("def"); + } + + public class MyType + { + // default + public int A { get; set; } + + // dbvalue + [DbValue(Name = "DbValue_B")] + public int B { get; set; } + + // standard enabled + [UseColumnAttribute] + [Column("StandardColumn_C")] + public int C { get; set; } + + // explicitly enabled + [UseColumnAttribute] + [Column("ExplicitColumn_D")] + public int D { get; set; } + + // dbValue & Column enabled + [UseColumnAttribute] + [DbValue(Name = "DbValue_E")] + [Column("Column_E")] + public int E { get; set; } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs new file mode 100644 index 00000000..de6bbcb2 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs @@ -0,0 +1,142 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\ColumnAttribute.input.cs", 13, 24)] + internal static global::System.Collections.Generic.IEnumerable Query0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName + // returns data: global::Foo.MyType + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory0.Instance); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 3826002220U when NormalizedEquals(name, "a"): + token = type == typeof(int) ? 0 : 5; // two tokens for right-typed and type-flexible + break; + case 1982833670U when NormalizedEquals(name, "dbvalueb"): + token = type == typeof(int) ? 1 : 6; + break; + case 315420707U when NormalizedEquals(name, "standardcolumnc"): + token = type == typeof(int) ? 2 : 7; + break; + case 3687481269U when NormalizedEquals(name, "explicitcolumnd"): + token = type == typeof(int) ? 3 : 8; + break; + case 2033166527U when NormalizedEquals(name, "dbvaluee"): + token = type == typeof(int) ? 4 : 9; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Foo.MyType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Foo.MyType result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.A = reader.GetInt32(columnOffset); + break; + case 5: + result.A = GetValue(reader, columnOffset); + break; + case 1: + result.B = reader.GetInt32(columnOffset); + break; + case 6: + result.B = GetValue(reader, columnOffset); + break; + case 2: + result.C = reader.GetInt32(columnOffset); + break; + case 7: + result.C = GetValue(reader, columnOffset); + break; + case 3: + result.D = reader.GetInt32(columnOffset); + break; + case 8: + result.D = GetValue(reader, columnOffset); + break; + case 4: + result.E = reader.GetInt32(columnOffset); + break; + case 9: + result.E = GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.txt b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.txt new file mode 100644 index 00000000..65c4cff4 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.txt @@ -0,0 +1,4 @@ +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 1 of 1 possible call-sites using 1 interceptors, 0 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.cs deleted file mode 100644 index e0aca6ea..00000000 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.cs +++ /dev/null @@ -1,1013 +0,0 @@ -#nullable enable -namespace Dapper.AOT // interceptors must be in a known namespace -{ - file static class DapperGeneratedInterceptors - { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 10, 24)] - internal static global::System.Collections.Generic.IEnumerable Query0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.ParameterlessCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory0.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 11, 24)] - internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.GetOnlyPropertiesViaConstructor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory1.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 12, 24)] - internal static global::System.Collections.Generic.IEnumerable Query2(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.RecordClass - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory2.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 13, 24)] - internal static global::System.Collections.Generic.IEnumerable Query3(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.RecordClassSimpleCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory3.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 14, 24)] - internal static global::System.Collections.Generic.IEnumerable Query4(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.RecordStruct - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory4.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 15, 24)] - internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.RecordStructSimpleCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory5.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 16, 24)] - internal static global::System.Collections.Generic.IEnumerable Query6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.InitPropsOnly - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory6.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 17, 24)] - internal static global::System.Collections.Generic.IEnumerable Query7(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.InitPropsAndDapperAotCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory7.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 18, 24)] - internal static global::System.Collections.Generic.IEnumerable Query8(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.OnlyNonDapperAotCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory8.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 19, 24)] - internal static global::System.Collections.Generic.IEnumerable Query9(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.SingleDefaultCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory9.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 20, 24)] - internal static global::System.Collections.Generic.IEnumerable Query10(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.MultipleDapperAotCtors - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory10.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryCustomConstruction.input.cs", 21, 24)] - internal static global::System.Collections.Generic.IEnumerable Query11(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName - // returns data: global::Foo.SingleDapperAotCtor - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory11.Instance); - - } - - private class CommonCommandFactory : global::Dapper.CommandFactory - { - public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) - { - var cmd = base.GetCommand(connection, sql, commandType, args); - // apply special per-provider command initialization logic for OracleCommand - if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) - { - cmd0.BindByName = true; - cmd0.InitialLONGFetchSize = -1; - - } - return cmd; - } - - } - - private static readonly CommonCommandFactory DefaultCommandFactory = new(); - - private sealed class RowFactory0 : global::Dapper.RowFactory - { - internal static readonly RowFactory0 Instance = new(); - private RowFactory0() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.ParameterlessCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.ParameterlessCtor result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory1 : global::Dapper.RowFactory - { - internal static readonly RowFactory1 Instance = new(); - private RowFactory1() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.GetOnlyPropertiesViaConstructor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - int value0 = default; - string? value1 = default; - double? value2 = default; - foreach (var token in tokens) - { - switch (token) - { - case 0: - value0 = reader.GetInt32(columnOffset); - break; - case 3: - value0 = GetValue(reader, columnOffset); - break; - case 1: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return new global::Foo.GetOnlyPropertiesViaConstructor(value0, value1, value2); - } - } - - private sealed class RowFactory2 : global::Dapper.RowFactory - { - internal static readonly RowFactory2 Instance = new(); - private RowFactory2() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.RecordClass Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.RecordClass result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory3 : global::Dapper.RowFactory - { - internal static readonly RowFactory3 Instance = new(); - private RowFactory3() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.RecordClassSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - int value0 = default; - string? value1 = default; - double? value2 = default; - foreach (var token in tokens) - { - switch (token) - { - case 0: - value0 = reader.GetInt32(columnOffset); - break; - case 3: - value0 = GetValue(reader, columnOffset); - break; - case 1: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return new global::Foo.RecordClassSimpleCtor(value0, value1, value2); - } - } - - private sealed class RowFactory4 : global::Dapper.RowFactory - { - internal static readonly RowFactory4 Instance = new(); - private RowFactory4() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.RecordStruct Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.RecordStruct result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory5 : global::Dapper.RowFactory - { - internal static readonly RowFactory5 Instance = new(); - private RowFactory5() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.RecordStructSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - int value0 = default; - string? value1 = default; - double? value2 = default; - foreach (var token in tokens) - { - switch (token) - { - case 0: - value0 = reader.GetInt32(columnOffset); - break; - case 3: - value0 = GetValue(reader, columnOffset); - break; - case 1: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return new global::Foo.RecordStructSimpleCtor(value0, value1, value2); - } - } - - private sealed class RowFactory6 : global::Dapper.RowFactory - { - internal static readonly RowFactory6 Instance = new(); - private RowFactory6() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.InitPropsOnly Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - int value0 = default; - string? value1 = default; - double? value2 = default; - foreach (var token in tokens) - { - switch (token) - { - case 0: - value0 = reader.GetInt32(columnOffset); - break; - case 3: - value0 = GetValue(reader, columnOffset); - break; - case 1: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return new global::Foo.InitPropsOnly - { - X = value0, - Y = value1, - Z = value2, - }; - } - } - - private sealed class RowFactory7 : global::Dapper.RowFactory - { - internal static readonly RowFactory7 Instance = new(); - private RowFactory7() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.InitPropsAndDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - int value0 = default; - string? value1 = default; - double? value2 = default; - foreach (var token in tokens) - { - switch (token) - { - case 0: - value0 = reader.GetInt32(columnOffset); - break; - case 3: - value0 = GetValue(reader, columnOffset); - break; - case 1: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - value1 = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - value2 = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return new global::Foo.InitPropsAndDapperAotCtor(value1) - { - X = value0, - Z = value2, - }; - } - } - - private sealed class RowFactory8 : global::Dapper.RowFactory - { - internal static readonly RowFactory8 Instance = new(); - private RowFactory8() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.OnlyNonDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.OnlyNonDapperAotCtor result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory9 : global::Dapper.RowFactory - { - internal static readonly RowFactory9 Instance = new(); - private RowFactory9() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.SingleDefaultCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.SingleDefaultCtor result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory10 : global::Dapper.RowFactory - { - internal static readonly RowFactory10 Instance = new(); - private RowFactory10() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.MultipleDapperAotCtors Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.MultipleDapperAotCtors result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class RowFactory11 : global::Dapper.RowFactory - { - internal static readonly RowFactory11 Instance = new(); - private RowFactory11() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 4245442695U when NormalizedEquals(name, "x"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 4228665076U when NormalizedEquals(name, "y"): - token = type == typeof(string) ? 1 : 4; - break; - case 4278997933U when NormalizedEquals(name, "z"): - token = type == typeof(double) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.SingleDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.SingleDapperAotCtor result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.X = reader.GetInt32(columnOffset); - break; - case 3: - result.X = GetValue(reader, columnOffset); - break; - case 1: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); - break; - case 5: - result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - - } -} -namespace System.Runtime.CompilerServices -{ - // this type is needed by the compiler to implement interceptors - it doesn't need to - // come from the runtime itself, though - - [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate - [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] - sealed file class InterceptsLocationAttribute : global::System.Attribute - { - public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) - { - _ = path; - _ = lineNumber; - _ = columnNumber; - } - } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.txt deleted file mode 100644 index 2203022e..00000000 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstruction.output.netfx.txt +++ /dev/null @@ -1,4 +0,0 @@ -Generator produced 1 diagnostics: - -Hidden DAP000 L1 C1 -Dapper.AOT handled 12 of 12 possible call-sites using 12 interceptors, 0 commands and 12 readers diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index 59aabc3f..db915432 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -54,6 +54,7 @@ public static Compilation CreateCompilation(string source, string name, string f MetadataReference.CreateFromFile(Assembly.Load("System.Data").Location), MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), + MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly.Location), #endif MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), MetadataReference.CreateFromFile(typeof(DbConnection).Assembly.Location), diff --git a/test/Dapper.AOT.Test/Verifiers/DAP042.cs b/test/Dapper.AOT.Test/Verifiers/DAP042.cs new file mode 100644 index 00000000..ca396c5a --- /dev/null +++ b/test/Dapper.AOT.Test/Verifiers/DAP042.cs @@ -0,0 +1,38 @@ +using Dapper.CodeAnalysis; +using System.Threading.Tasks; +using Xunit; +using static Dapper.CodeAnalysis.DapperAnalyzer; + +namespace Dapper.AOT.Test.Verifiers; + +public class DAP042 : Verifier +{ + [Fact] + public Task ParameterNameOverrideConflict() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + using System.ComponentModel.DataAnnotations.Schema; + + [DapperAot] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("def"); + } + } + class MyType + { + [UseColumnAttribute] + [{|#0:DbValue(Name = "DbValuePropName")|}] + [{|#1:Column("ColumnAttrPropName")|}] + public int PropName { get; set; } + } + """, + DefaultConfig, + [ + Diagnostic(Diagnostics.ParameterNameOverrideConflict).WithLocation(0).WithLocation(1).WithArguments("DbValuePropName"), + ] + ); + +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Verifiers/DAP043.cs b/test/Dapper.AOT.Test/Verifiers/DAP043.cs new file mode 100644 index 00000000..2330ce4b --- /dev/null +++ b/test/Dapper.AOT.Test/Verifiers/DAP043.cs @@ -0,0 +1,36 @@ +using Dapper.CodeAnalysis; +using System.Threading.Tasks; +using Xunit; +using static Dapper.CodeAnalysis.DapperAnalyzer; + +namespace Dapper.AOT.Test.Verifiers; + +public class DAP043 : Verifier +{ + [Fact] + public Task UseColumnAttributeNotSpecified() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + using System.ComponentModel.DataAnnotations.Schema; + + [DapperAot] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("def"); + } + } + class MyType + { + [{|#0:Column("ColumnAttrPropName")|}] + public int PropName { get; set; } + } + """, + DefaultConfig, + [ + Diagnostic(Diagnostics.UseColumnAttributeNotSpecified).WithLocation(0), + ] + ); + +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Verifiers/Verifier.cs b/test/Dapper.AOT.Test/Verifiers/Verifier.cs index 6b718bcb..36c2d48a 100644 --- a/test/Dapper.AOT.Test/Verifiers/Verifier.cs +++ b/test/Dapper.AOT.Test/Verifiers/Verifier.cs @@ -103,6 +103,7 @@ internal Task ExecuteAsync(AnalyzerTest test, string source, } test.TestState.AdditionalReferences.Add(typeof(System.Data.SqlClient.SqlConnection).Assembly); test.TestState.AdditionalReferences.Add(typeof(Microsoft.Data.SqlClient.SqlConnection).Assembly); + test.TestState.AdditionalReferences.Add(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly); if (transforms is not null) { test.SolutionTransforms.AddRange(transforms); From 828b8abb72edce3a57d3a6128846519ba8b0aba5 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sat, 18 Nov 2023 01:26:53 +0100 Subject: [PATCH 04/12] fix indents --- docs/rules/DAP042.md | 6 +++--- docs/rules/DAP043.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/rules/DAP042.md b/docs/rules/DAP042.md index 0b59ece8..8edab674 100644 --- a/docs/rules/DAP042.md +++ b/docs/rules/DAP042.md @@ -10,7 +10,7 @@ Bad: ``` csharp class MyType { - [DbValue(Name = "MyOtherOtherName")] + [DbValue(Name = "MyOtherOtherName")] [UseColumnAttribute] [Column("MyOtherName")] public int MyThisName { get; set; } @@ -22,7 +22,7 @@ Good: ``` csharp class MyType { - [DbValue(Name = "MyOtherOtherName")] + [DbValue(Name = "MyOtherOtherName")] // without [Column] and [UseColumnAttribute] public int MyThisName { get; set; } } @@ -33,7 +33,7 @@ Another good: ``` csharp class MyType { - // without [DbValue] + // without [DbValue] [UseColumnAttribute] [Column("MyOtherName")] public int MyThisName { get; set; } diff --git a/docs/rules/DAP043.md b/docs/rules/DAP043.md index e769f4ec..46c8a909 100644 --- a/docs/rules/DAP043.md +++ b/docs/rules/DAP043.md @@ -21,7 +21,7 @@ Good: ``` csharp class MyType { - [UseColumnAttribute] + [UseColumnAttribute] [Column("MyOtherName")] public int MyThisName { get; set; } } From 8f0fdce55f719ba7a8889a6fc35cb120fe5c39fe Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sat, 18 Nov 2023 01:28:14 +0100 Subject: [PATCH 05/12] fix indent x2 --- Directory.Packages.props | 76 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7e47942f..5c9ea5f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From aa6e554916d976e1b9a786ae3cee1cd44af1dd8a Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sat, 18 Nov 2023 01:28:43 +0100 Subject: [PATCH 06/12] rollback --- Directory.Packages.props | 76 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 5c9ea5f2..7e47942f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fb740ea97a9c631ebf9a139e1977f1a40b92ffe1 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sat, 18 Nov 2023 10:44:06 +0100 Subject: [PATCH 07/12] fix `GetLocation(attributeName)` logic --- src/Dapper.AOT.Analyzers/Internal/Inspection.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index c7cb4e06..b829d9ac 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -547,9 +547,11 @@ public override bool Equals(object obj) => obj is ElementMember other return GetLocation(); } - return allowNonDapperLocations + var result = allowNonDapperLocations ? GetAttribute(Member, attributeName)?.ApplicationSyntaxReference?.GetSyntax()?.GetLocation() : GetDapperAttribute(Member, attributeName)?.ApplicationSyntaxReference?.GetSyntax()?.GetLocation(); + + return result ?? GetLocation(); } } From 0294ec2acbb00b76c0a614c99d461632c2dd82f3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 20 Nov 2023 11:23:25 +0000 Subject: [PATCH 08/12] Update DAP043.md --- docs/rules/DAP043.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/DAP043.md b/docs/rules/DAP043.md index 46c8a909..c26863ca 100644 --- a/docs/rules/DAP043.md +++ b/docs/rules/DAP043.md @@ -1,6 +1,6 @@ # DAP043 -If you are looking at `DAP043`, then most probably you wanted to use [[Column]](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) +If you are looking at `DAP043`, then most probably you wanted to use [`[Column]`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) attribute to override the name of the type member. However, due to [historical reasons](https://stackoverflow.com/a/77073456) you need to add a "marker attribute" `[UseColumnAttribute]` @@ -25,4 +25,4 @@ class MyType [Column("MyOtherName")] public int MyThisName { get; set; } } -``` \ No newline at end of file +``` From a4646870f3e885060a270ae5f87c4485a73b41dc Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 20 Nov 2023 11:25:02 +0000 Subject: [PATCH 09/12] Update DAP042.md --- docs/rules/DAP042.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/DAP042.md b/docs/rules/DAP042.md index 8edab674..fc23f7c3 100644 --- a/docs/rules/DAP042.md +++ b/docs/rules/DAP042.md @@ -1,6 +1,6 @@ # DAP042 -Usage of [[Column]](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) +Usage of [`[Column]`](https://learn.microsoft.com/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) attribute and `[DbValue]` attributes are conflicting - Dapper will choose either one or another's name override. This conflict can lead to unexpected behavior, so we recommend to use only one of them. @@ -38,4 +38,4 @@ class MyType [Column("MyOtherName")] public int MyThisName { get; set; } } -``` \ No newline at end of file +``` From 813c04f3f32be6a67c30cbebb415bc8341938980 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 20 Nov 2023 11:25:27 +0000 Subject: [PATCH 10/12] Update DAP043.md --- docs/rules/DAP043.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/DAP043.md b/docs/rules/DAP043.md index c26863ca..482ee782 100644 --- a/docs/rules/DAP043.md +++ b/docs/rules/DAP043.md @@ -1,6 +1,6 @@ # DAP043 -If you are looking at `DAP043`, then most probably you wanted to use [`[Column]`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) +If you are looking at `DAP043`, then most probably you wanted to use [`[Column]`](https://learn.microsoft.com/dotnet/api/system.componentmodel.dataannotations.schema.columnattribute) attribute to override the name of the type member. However, due to [historical reasons](https://stackoverflow.com/a/77073456) you need to add a "marker attribute" `[UseColumnAttribute]` From ec7cf8f54edf83ea67b3006c185b8685295fe14c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 20 Nov 2023 11:35:03 +0000 Subject: [PATCH 11/12] Update Inspection.cs --- src/Dapper.AOT.Analyzers/Internal/Inspection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index b829d9ac..53188e1b 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -919,7 +919,7 @@ private static bool TryGetAttributeValue(AttributeData? attrib, string name, int index = 0; foreach (var p in ctor.Parameters) { - if (p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + if (StringComparer.InvariantCultureIgnoreCase.Equals(p.Name, name)) { value = Parse(attrib.ConstructorArguments[index], out isNull); return true; From f692ffe0b022d898b122da69a600e758a389b21f Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 20 Nov 2023 18:22:21 +0100 Subject: [PATCH 12/12] add attr usage --- src/Dapper.AOT/UseColumnAttributeAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dapper.AOT/UseColumnAttributeAttribute.cs b/src/Dapper.AOT/UseColumnAttributeAttribute.cs index 1d2d73ad..cd55291e 100644 --- a/src/Dapper.AOT/UseColumnAttributeAttribute.cs +++ b/src/Dapper.AOT/UseColumnAttributeAttribute.cs @@ -5,6 +5,7 @@ namespace Dapper /// /// Specifies whether to use [System.ComponentModel.DataAnnotations.Schema.ColumnAttribute] for additional behavioral configuration /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public class UseColumnAttributeAttribute : Attribute { /// enable usage of [Column] attribute