From ca6a123e1a9f0385881b01da297296260b399e57 Mon Sep 17 00:00:00 2001 From: Paul Welter Date: Thu, 8 Aug 2024 22:11:28 -0500 Subject: [PATCH] fix issue with reader generation and column names --- .../DataReaderFactoryGenerator.cs | 54 ++++++++++++++++++- .../DataReaderFactoryWriter.cs | 10 ++-- .../Models/EntityProperty.cs | 4 ++ .../Extensions/DataRecordExtensions.cs | 8 +-- test/FluentCommand.Entities/TableTest.cs | 31 +++++++++++ .../DataReaderFactoryWriterTests.cs | 10 ++-- ...erFactoryWriterTests.Generate.verified.txt | 10 ++-- .../DataQueryTests.cs | 14 +++++ .../Scripts/Script001.Tracker.Schema.sql | 12 +++++ .../Scripts/Script002.Tracker.Data.sql | 19 +++++++ 10 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 test/FluentCommand.Entities/TableTest.cs diff --git a/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs b/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs index f5094edd..caef3f6f 100644 --- a/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs +++ b/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Reflection; +using System.Xml.Linq; using FluentCommand.Generators.Internal; using FluentCommand.Generators.Models; @@ -171,7 +172,15 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str // look for custom field converter var attributes = propertySymbol.GetAttributes(); if (attributes == null || attributes.Length == 0) - return new EntityProperty(propertyName, propertyType, parameterName); + { + return new EntityProperty( + propertyName, + propertyName, + propertyType, + parameterName); + } + + var columnName = GetColumnName(attributes) ?? propertyName; var converter = attributes .FirstOrDefault(a => a.AttributeClass is @@ -181,7 +190,13 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str }); if (converter == null) - return new EntityProperty(propertyName, propertyType, parameterName); + { + return new EntityProperty( + propertyName, + columnName, + propertyType, + parameterName); + } // attribute contructor var converterType = converter.ConstructorArguments.FirstOrDefault(); @@ -189,6 +204,7 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str { return new EntityProperty( propertyName, + columnName, propertyType, parameterName, converterSymbol.ToDisplayString()); @@ -205,6 +221,7 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str return new EntityProperty( propertyName, + columnName, propertyType, parameterName, converterString); @@ -212,6 +229,7 @@ private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, str return new EntityProperty( propertyName, + columnName, propertyType, parameterName); } @@ -243,4 +261,36 @@ private static bool IsIncluded(IPropertySymbol propertySymbol) return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public; } + + private static string GetColumnName(ImmutableArray attributes) + { + var columnAttribute = attributes + .FirstOrDefault(a => a.AttributeClass is + { + Name: "ColumnAttribute", + ContainingNamespace: + { + Name: "Schema", + ContainingNamespace: + { + Name: "DataAnnotations", + ContainingNamespace: + { + Name: "ComponentModel", + ContainingNamespace.Name: "System" + } + } + } + }); + + if (columnAttribute == null) + return null; + + // attribute contructor [Column("Name")] + var converterType = columnAttribute.ConstructorArguments.FirstOrDefault(); + if (converterType.Value is string stringValue) + return stringValue; + + return null; + } } diff --git a/src/FluentCommand.Generators/DataReaderFactoryWriter.cs b/src/FluentCommand.Generators/DataReaderFactoryWriter.cs index c4396ba6..295b889b 100644 --- a/src/FluentCommand.Generators/DataReaderFactoryWriter.cs +++ b/src/FluentCommand.Generators/DataReaderFactoryWriter.cs @@ -364,13 +364,9 @@ private static void WriteEntityFactory(IndentedStringBuilder codeBuilder, Entity var fieldName = CamelCase(entityProperty.PropertyName); codeBuilder - .Append("case nameof(") - .Append(entity.EntityNamespace) - .Append(".") - .Append(entity.EntityName) - .Append(".") - .Append(entityProperty.PropertyName) - .AppendLine("):"); + .Append("case \"") + .Append(entityProperty.ColumnName) + .AppendLine("\":"); if (string.IsNullOrEmpty(entityProperty.ConverterName)) { diff --git a/src/FluentCommand.Generators/Models/EntityProperty.cs b/src/FluentCommand.Generators/Models/EntityProperty.cs index c3e17d44..82073c45 100644 --- a/src/FluentCommand.Generators/Models/EntityProperty.cs +++ b/src/FluentCommand.Generators/Models/EntityProperty.cs @@ -6,11 +6,13 @@ 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; @@ -18,6 +20,8 @@ public EntityProperty( public string PropertyName { get; } + public string ColumnName { get; } + public string PropertyType { get; } public string ParameterName { get; } diff --git a/src/FluentCommand/Extensions/DataRecordExtensions.cs b/src/FluentCommand/Extensions/DataRecordExtensions.cs index 55111e93..8d8c2b60 100644 --- a/src/FluentCommand/Extensions/DataRecordExtensions.cs +++ b/src/FluentCommand/Extensions/DataRecordExtensions.cs @@ -450,13 +450,7 @@ public static object GetValue(this IDataRecord dataRecord, string name) public static T GetValue(this IDataRecord dataRecord, string name) { int ordinal = dataRecord.GetOrdinal(name); - if (dataRecord.IsDBNull(ordinal)) - return default; - - if (dataRecord is DbDataReader dataReader) - return dataReader.GetFieldValue(ordinal); - - return (T)dataRecord.GetValue(ordinal); + return dataRecord.GetValue(ordinal); } /// diff --git a/test/FluentCommand.Entities/TableTest.cs b/test/FluentCommand.Entities/TableTest.cs new file mode 100644 index 00000000..9703fef5 --- /dev/null +++ b/test/FluentCommand.Entities/TableTest.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace FluentCommand.Entities; + +[Table("Table1 $ Test", Schema = "dbo")] +public class TableTest +{ + private const string BlahColumn = "Blah #"; + + [Key] + [Column("Test$")] + public string Test { get; set; } = null!; + + [Column(BlahColumn)] + public string Blah { get; set; } + + [Column("Table Example ID")] + public int? TableExampleID { get; set; } + + public int? TableExampleObject { get; set; } + + [Column("1stNumber")] + public string FirstNumber { get; set; } + + [Column("123Street")] + public string Street { get; set; } + + [Column("123 Test 123")] + public string Test123 { get; set; } +} diff --git a/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs b/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs index 06571ba5..d1e2525a 100644 --- a/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs +++ b/test/FluentCommand.Generators.Tests/DataReaderFactoryWriterTests.cs @@ -15,11 +15,11 @@ public async Task Generate() "Status", new EntityProperty[] { - new("Id", typeof(int).FullName), - new("Name", typeof(string).FullName), - new("IsActive", typeof(bool).FullName), - new("Updated", typeof(DateTimeOffset).FullName), - new("RowVersion", typeof(byte[]).FullName), + new("Id", "Id", typeof(int).FullName), + new("Name", "Name", typeof(string).FullName), + new("IsActive", "IsActive", typeof(bool).FullName), + new("Updated", "Updated", typeof(DateTimeOffset).FullName), + new("RowVersion", "RowVersion", typeof(byte[]).FullName), }.ToImmutableArray() ); diff --git a/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt b/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt index 2e6ea1a4..2c7afb2d 100644 --- a/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt +++ b/test/FluentCommand.Generators.Tests/Snapshots/DataReaderFactoryWriterTests.Generate.verified.txt @@ -112,19 +112,19 @@ namespace FluentCommand.Entities var __name = dataRecord.GetName(__index); switch (__name) { - case nameof(FluentCommand.Entities.Status.Id): + case "Id": v_id = dataRecord.GetInt32(__index); break; - case nameof(FluentCommand.Entities.Status.Name): + case "Name": v_name = dataRecord.GetString(__index); break; - case nameof(FluentCommand.Entities.Status.IsActive): + case "IsActive": v_isActive = dataRecord.GetBoolean(__index); break; - case nameof(FluentCommand.Entities.Status.Updated): + case "Updated": v_updated = dataRecord.GetDateTimeOffset(__index); break; - case nameof(FluentCommand.Entities.Status.RowVersion): + case "RowVersion": v_rowVersion = dataRecord.GetBytes(__index); break; } diff --git a/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs b/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs index 9f3d67e0..23af5372 100644 --- a/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs @@ -454,4 +454,18 @@ public async System.Threading.Tasks.Task SqlQueryDataTypeAsync() results.Should().NotBeNull(); } + [Fact] + public async System.Threading.Tasks.Task SqlQueryTableTestAsync() + { + await using var session = Services.GetRequiredService(); + session.Should().NotBeNull(); + + var results = await session + .Sql(builder => builder + .Select() + ) + .QueryAsync(); + + results.Should().NotBeNull(); + } } diff --git a/test/FluentCommand.SqlServer.Tests/Scripts/Script001.Tracker.Schema.sql b/test/FluentCommand.SqlServer.Tests/Scripts/Script001.Tracker.Schema.sql index 8c77ea0f..a392e048 100644 --- a/test/FluentCommand.SqlServer.Tests/Scripts/Script001.Tracker.Schema.sql +++ b/test/FluentCommand.SqlServer.Tests/Scripts/Script001.Tracker.Schema.sql @@ -185,6 +185,18 @@ CREATE TABLE [dbo].[DataType] ( CONSTRAINT [PK_DataType] PRIMARY KEY ([Id]) ); +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Table1 $ Test]') AND type in (N'U')) +CREATE TABLE [dbo].[Table1 $ Test] ( + [Test$] char(10) NOT NULL, + [Blah #] char(10) NULL, + [Table Example ID] int NULL, + [TableExampleObject] int NULL, + [1stNumber] nvarchar(50) NULL, + [123Street] nvarchar(50) NULL, + [123 Test 123] nvarchar(50) NULL, + CONSTRAINT [PK_Table1 $ Test] PRIMARY KEY ([Test$]) +); + -- Types CREATE TYPE [dbo].[UserImportType] AS TABLE ( diff --git a/test/FluentCommand.SqlServer.Tests/Scripts/Script002.Tracker.Data.sql b/test/FluentCommand.SqlServer.Tests/Scripts/Script002.Tracker.Data.sql index 75d61755..204257c2 100644 --- a/test/FluentCommand.SqlServer.Tests/Scripts/Script002.Tracker.Data.sql +++ b/test/FluentCommand.SqlServer.Tests/Scripts/Script002.Tracker.Data.sql @@ -120,3 +120,22 @@ WHEN NOT MATCHED BY TARGET THEN WHEN MATCHED THEN UPDATE SET t.[Name] = s.[Name], t.[Boolean] = s.[Boolean], t.[Short] = s.[Short], t.[Long] = s.[Long], t.[Float] = s.[Float], t.[Double] = s.[Double], t.[Decimal] = s.[Decimal], t.[DateTime] = s.[DateTime], t.[DateTimeOffset] = s.[DateTimeOffset], t.[Guid] = s.[Guid], t.[TimeSpan] = s.[TimeSpan], t.[DateOnly] = s.[DateOnly], t.[TimeOnly] = s.[TimeOnly], t.[BooleanNull] = s.[BooleanNull], t.[ShortNull] = s.[ShortNull], t.[LongNull] = s.[LongNull], t.[FloatNull] = s.[FloatNull], t.[DoubleNull] = s.[DoubleNull], t.[DecimalNull] = s.[DecimalNull], t.[DateTimeNull] = s.[DateTimeNull], t.[DateTimeOffsetNull] = s.[DateTimeOffsetNull], t.[GuidNull] = s.[GuidNull], t.[TimeSpanNull] = s.[TimeSpanNull], t.[DateOnlyNull] = s.[DateOnlyNull], t.[TimeOnlyNull] = s.[TimeOnlyNull] OUTPUT $action as MergeAction; + +/* Table [dbo].[Table1 $ Test] data */ + +MERGE INTO [dbo].[Table1 $ Test] AS t +USING +( + VALUES + (N'testing ', N'value ', 123, 456, N'123 Main', N'City, MN', N'55555') +) +AS s +([Test$], [Blah #], [Table Example ID], [TableExampleObject], [1stNumber], [123Street], [123 Test 123]) +ON (t.[Test$] = s.[Test$]) +WHEN NOT MATCHED BY TARGET THEN + INSERT ([Test$], [Blah #], [Table Example ID], [TableExampleObject], [1stNumber], [123Street], [123 Test 123]) + VALUES (s.[Test$], s.[Blah #], s.[Table Example ID], s.[TableExampleObject], s.[1stNumber], s.[123Street], s.[123 Test 123]) +WHEN MATCHED THEN + UPDATE SET t.[Blah #] = s.[Blah #], t.[Table Example ID] = s.[Table Example ID], t.[TableExampleObject] = s.[TableExampleObject], t.[1stNumber] = s.[1stNumber], t.[123Street] = s.[123Street], t.[123 Test 123] = s.[123 Test 123] +OUTPUT $action as MergeAction; +