diff --git a/.editorconfig b/.editorconfig index 0ca722d..1884bec 100644 --- a/.editorconfig +++ b/.editorconfig @@ -44,6 +44,9 @@ dotnet_diagnostic.RCS1037.severity = none # RCS1227: Validate arguments correctly dotnet_diagnostic.RCS1227.severity = none +# RCS1124: Inline local variable +dotnet_diagnostic.RCS1124.severity = suggestion + [*.{cs,vb}] #### Naming styles #### diff --git a/CHANGELOG.md b/CHANGELOG.md index 370688f..11d7064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] + +### Added +- Support for column name mapping via System.ComponentModel.DataAnnotations.Schema.ColumnAttribute (Fixes [issue #1](https://github.com/rent-a-developer/DbConnectionPlus/issues/1)) +- Throw helper for common exceptions + +### Changed +- Updated all dependencies to latest stable versions + ## [1.0.0] - 2026-01-24 ### Added diff --git a/README.md b/README.md index 8cc532d..4fcf945 100644 --- a/README.md +++ b/README.md @@ -28,61 +28,55 @@ Other database systems and database connectors can be supported by implementing All examples in this document use SQL Server. ## Table of contents -- [ DbConnectionPlus](#-dbconnectionplus) - - [Table of contents](#table-of-contents) - - [Installation](#installation) - - [Quick start](#quick-start) - - [Examples](#examples) - - [Parameters via interpolated strings](#parameters-via-interpolated-strings) - - [On-the-fly temporary tables via interpolated strings](#on-the-fly-temporary-tables-via-interpolated-strings) - - [Enum support](#enum-support) - - [API summary](#api-summary) - - [Attributes](#attributes) - - [`System.ComponentModel.DataAnnotations.Schema.TableAttribute`](#systemcomponentmodeldataannotationsschematableattribute) - - [`System.ComponentModel.DataAnnotations.KeyAttribute`](#systemcomponentmodeldataannotationskeyattribute) - - [`System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute`](#systemcomponentmodeldataannotationsschemadatabasegeneratedattribute) - - [`System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute`](#systemcomponentmodeldataannotationsschemanotmappedattribute) - - [Configuration](#configuration) - - [EnumSerializationMode](#enumserializationmode) - - [InterceptDbCommand](#interceptdbcommand) - - [General-purpose methods](#general-purpose-methods) - - [ExecuteNonQuery / ExecuteNonQueryAsync](#executenonquery--executenonqueryasync) - - [ExecuteReader / ExecuteReaderAsync](#executereader--executereaderasync) - - [ExecuteScalar / ExecuteScalarAsync](#executescalar--executescalarasync) - - [Exists / ExistsAsync](#exists--existsasync) - - [Query methods](#query-methods) - - [Query / QueryAsync](#query--queryasync) - - [QueryFirst / QueryFirstAsync](#queryfirst--queryfirstasync) - - [QueryFirstOrDefault / QueryFirstOrDefaultAsync](#queryfirstordefault--queryfirstordefaultasync) - - [QuerySingle / QuerySingleAsync](#querysingle--querysingleasync) - - [QuerySingleOrDefault / QuerySingleOrDefaultAsync](#querysingleordefault--querysingleordefaultasync) - - [Query\ / QueryAsync\](#queryt--queryasynct) - - [QueryFirst\ / QueryFirstAsync\](#queryfirstt--queryfirstasynct) - - [QueryFirstOrDefault\ / QueryFirstOrDefaultAsync\](#queryfirstordefaultt--queryfirstordefaultasynct) - - [QuerySingle\ / QuerySingleAsync\](#querysinglet--querysingleasynct) - - [QuerySingleOrDefault\ / QuerySingleOrDefaultAsync\](#querysingleordefaultt--querysingleordefaultasynct) - - [Entity manipulation methods](#entity-manipulation-methods) - - [InsertEntities / InsertEntitiesAsync](#insertentities--insertentitiesasync) - - [InsertEntity / InsertEntityAsync](#insertentity--insertentityasync) - - [UpdateEntities / UpdateEntitiesAsync](#updateentities--updateentitiesasync) - - [UpdateEntity / UpdateEntityAsync](#updateentity--updateentityasync) - - [DeleteEntities / DeleteEntitiesAsync](#deleteentities--deleteentitiesasync) - - [DeleteEntity / DeleteEntityAsync](#deleteentity--deleteentityasync) - - [Special helpers](#special-helpers) - - [Parameter(value)](#parametervalue) - - [TemporaryTable(values)](#temporarytablevalues) - - [Custom database adapter](#custom-database-adapter) - - [Benchmarks](#benchmarks) - - [Running the benchmarks](#running-the-benchmarks) - - [Running the unit tests](#running-the-unit-tests) - - [Running the integration tests](#running-the-integration-tests) - - [Contributing](#contributing) - - [License](#license) - - [Documentation](#documentation) - - [Change Log](#change-log) - - [Contributors](#contributors) - -## Installation +- **[Quick start](#quick-start)** +- [Examples](#examples) +- [Parameters via interpolated strings](#parameters-via-interpolated-strings) +- [On-the-fly temporary tables via interpolated strings](#on-the-fly-temporary-tables-via-interpolated-strings) +- [Enum support](#enum-support) +- [API summary](#api-summary) +- [Configuration](#configuration) + - [EnumSerializationMode](#enumserializationmode) + - [InterceptDbCommand](#interceptdbcommand) + - [Entity Mapping](#entity-mapping) + - [Data annotation attributes](#data-annotation-attributes) +- [General-purpose methods](#general-purpose-methods) + - [ExecuteNonQuery / ExecuteNonQueryAsync](#executenonquery--executenonqueryasync) + - [ExecuteReader / ExecuteReaderAsync](#executereader--executereaderasync) + - [ExecuteScalar / ExecuteScalarAsync](#executescalar--executescalarasync) + - [Exists / ExistsAsync](#exists--existsasync) +- [Query methods](#query-methods) + - [Query / QueryAsync](#query--queryasync) + - [QueryFirst / QueryFirstAsync](#queryfirst--queryfirstasync) + - [QueryFirstOrDefault / QueryFirstOrDefaultAsync](#queryfirstordefault--queryfirstordefaultasync) + - [QuerySingle / QuerySingleAsync](#querysingle--querysingleasync) + - [QuerySingleOrDefault / QuerySingleOrDefaultAsync](#querysingleordefault--querysingleordefaultasync) + - [Query\ / QueryAsync\](#queryt--queryasynct) + - [QueryFirst\ / QueryFirstAsync\](#queryfirstt--queryfirstasynct) + - [QueryFirstOrDefault\ / QueryFirstOrDefaultAsync\](#queryfirstordefaultt--queryfirstordefaultasynct) + - [QuerySingle\ / QuerySingleAsync\](#querysinglet--querysingleasynct) + - [QuerySingleOrDefault\ / QuerySingleOrDefaultAsync\](#querysingleordefaultt--querysingleordefaultasynct) +- [Entity manipulation methods](#entity-manipulation-methods) + - [InsertEntities / InsertEntitiesAsync](#insertentities--insertentitiesasync) + - [InsertEntity / InsertEntityAsync](#insertentity--insertentityasync) + - [UpdateEntities / UpdateEntitiesAsync](#updateentities--updateentitiesasync) + - [UpdateEntity / UpdateEntityAsync](#updateentity--updateentityasync) + - [DeleteEntities / DeleteEntitiesAsync](#deleteentities--deleteentitiesasync) + - [DeleteEntity / DeleteEntityAsync](#deleteentity--deleteentityasync) +- [Special helpers](#special-helpers) + - [Parameter(value)](#parametervalue) + - [TemporaryTable(values)](#temporarytablevalues) +- [Custom database adapter](#custom-database-adapter) +- [Benchmarks](#benchmarks) +- [Running the benchmarks](#running-the-benchmarks) +- [Running the unit tests](#running-the-unit-tests) +- [Running the integration tests](#running-the-integration-tests) +- [Contributing](#contributing) +- [License](#license) +- [Documentation](#documentation) +- [Change Log](#change-log) +- [Contributors](#contributors) + +## Quick start First, [install NuGet](https://docs.nuget.org/docs/start-here/installing-nuget). Then install the [NuGet package](https://www.nuget.org/packages/RentADeveloper.DbConnectionPlus/) from the package @@ -91,7 +85,6 @@ manager console: PM> Install-Package RentADeveloper.DbConnectionPlus ``` -## Quick start Import the library and the static helpers: ```csharp @@ -329,9 +322,16 @@ corresponding enum values. ## API summary -Attributes: +Configuration: +- [EnumSerializationMode](#enumserializationmode) - Configure how enum values are serialized when sent to the database +- [InterceptDbCommand](#interceptdbcommand) - Configure a delegate to intercept `DbCommand`s executed by DbConnectionPlus + +Entity mapping: +Data annotation attributes: - [`System.ComponentModel.DataAnnotations.Schema.TableAttribute`](#systemcomponentmodeldataannotationsschematableattribute) Specify the name of the table where entities of an entity type are stored +- [`System.ComponentModel.DataAnnotations.Schema.ColumnAttribute`](#systemcomponentmodeldataannotationsschemacolumnattribute) +Specify the name of the column where a property of an entity type is stored - [`System.ComponentModel.DataAnnotations.KeyAttribute`](#systemcomponentmodeldataannotationskeyattribute) Specify the key property / properties of an entity type - [`System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute`](#systemcomponentmodeldataannotationsschemadatabasegeneratedattribute) @@ -339,10 +339,6 @@ Specify that a property of an entity type is generated by the database - [`System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute`](#systemcomponentmodeldataannotationsschemanotmappedattribute) Specify that a property of an entity type is not mapped to a column in the database -Configuration: -- [EnumSerializationMode](#enumserializationmode) - Configure how enum values are serialized when sent to the database -- [InterceptDbCommand](#interceptdbcommand) - Configure a delegate to intercept `DbCommand`s executed by DbConnectionPlus - General-purpose methods: - [ExecuteNonQuery / ExecuteNonQueryAsync](#executenonquery--executenonqueryasync) - Execute a non-query and return number of affected rows @@ -376,63 +372,8 @@ Special helpers: - [TemporaryTable(values)](#temporarytablevalues) - Create a temporary table from a sequence of values and reference it inside an SQL statement -### Attributes -This library uses the following attributes: - -#### `System.ComponentModel.DataAnnotations.Schema.TableAttribute` -Use this attribute to specify the name of the table where entities of an entity type are stored in the database: -```csharp -[Table("Products")] -public class Product { ... } -``` -If you don't specify the table name using this attribute, the singular name of the entity type -(not including its namespace) is used as the table name. - -#### `System.ComponentModel.DataAnnotations.KeyAttribute` -Use this attribute to specify the property / properties of an entity type by which entities of that type are -identified (usually the primary key / keys): -```csharp -class Product -{ - [Key] - public Int64 Id { get; set; } -} -``` - -This attribute must be used for entities passed to the following methods: -- [InsertEntities / InsertEntitiesAsync](#insertentities--insertentitiesasync) -- [InsertEntity / InsertEntityAsync](#insertentity--insertentityasync) -- [UpdateEntities / UpdateEntitiesAsync](#updateentities--updateentitiesasync) -- [UpdateEntity / UpdateEntityAsync](#updateentity--updateentityasync) -- [DeleteEntities / DeleteEntitiesAsync](#deleteentities--deleteentitiesasync) -- [DeleteEntity / DeleteEntityAsync](#deleteentity--deleteentityasync) - -#### `System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute` -Use this attribute to specify that a property of an entity type is generated by the database: -```csharp -class Product -{ - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public Int64 Id { get; set; } -} -``` -Properties marked with this attribute are ignored (unless `DatabaseGeneratedOption.None` is used) when inserting new -entities into the database or updating existing entities. -When an entity is inserted or updated, the value of the property is read back from the database and set on the entity. - -#### `System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute` -Use this attribute to specify that a property of an entity type should be ignored and not mapped to a column: -```csharp -public class OrderItem -{ - [NotMapped] - public Decimal TotalPrice => this.UnitPrice * this.Quantity; -} -``` -Properties marked with this attribute are ignored by DbConnectionPlus. -They are never read from the database and never written to the database. - ### Configuration + #### EnumSerializationMode Use `DbConnectionExtensions.EnumSerializationMode` to configure how enum values are serialized when they are sent to a database. @@ -498,6 +439,68 @@ DbConnectionExtensions.InterceptDbCommand = (dbCommand, temporaryTables) => See [DbCommandLogger](https://github.com/rent-a-developer/DbConnectionPlus/blob/main/tests/DbConnectionPlus.IntegrationTests/TestHelpers/DbCommandLogger.cs) for an example of logging executed commands. +#### Entity Mapping + +##### Data annotation attributes + +You can use the following attributes to configure how entity types are mapped to database tables and columns: + +###### `System.ComponentModel.DataAnnotations.Schema.TableAttribute` +Use this attribute to specify the name of the table where entities of an entity type are stored in the database: +```csharp +[Table("Products")] +public class Product { ... } +``` +If you don't specify the table name using this attribute, the singular name of the entity type +(not including its namespace) is used as the table name. + +###### `System.ComponentModel.DataAnnotations.Schema.ColumnAttribute` +Use this attribute to specify the name of the column where a property of an entity type is stored in the database: +```csharp +class Product +{ + [Column("ProductName")] + public String Name { get; set; } +} +``` +If you don't specify the column name using this attribute, the property name is used as the column name. + +###### `System.ComponentModel.DataAnnotations.KeyAttribute` +Use this attribute to specify the property / properties of an entity type by which entities of that type are +identified (usually the primary key / keys): +```csharp +class Product +{ + [Key] + public Int64 Id { get; set; } +} +``` + +###### `System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute` +Use this attribute to specify that a property of an entity type is generated by the database: +```csharp +class Product +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Int64 Id { get; set; } +} +``` +Properties marked with this attribute are ignored (unless `DatabaseGeneratedOption.None` is used) when inserting new +entities into the database or updating existing entities. +When an entity is inserted or updated, the value of the property is read back from the database and set on the entity. + +###### `System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute` +Use this attribute to specify that a property of an entity type should be ignored and not mapped to a column: +```csharp +public class OrderItem +{ + [NotMapped] + public Decimal TotalPrice => this.UnitPrice * this.Quantity; +} +``` +Properties marked with this attribute are ignored by DbConnectionPlus. +They are never read from the database and never written to the database. + ### General-purpose methods #### ExecuteNonQuery / ExecuteNonQueryAsync diff --git a/src/DbConnectionPlus/DatabaseAdapters/Constants.cs b/src/DbConnectionPlus/DatabaseAdapters/Constants.cs index a3344d1..5407d5e 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/Constants.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/Constants.cs @@ -12,4 +12,9 @@ public static class Constants /// The string to use to indent parts of SQL statements. /// public const String Indent = " "; + + /// + /// The name to use for the single column of single column temporary tables. + /// + public const String SingleColumnTemporaryTableColumnName = "Value"; } diff --git a/src/DbConnectionPlus/DatabaseAdapters/ITemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/ITemporaryTableBuilder.cs index 0b06a3e..d69e768 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/ITemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/ITemporaryTableBuilder.cs @@ -11,10 +11,10 @@ public interface ITemporaryTableBuilder /// /// Builds a temporary table and populates it with the specified values. /// - /// The database connection to use to build the temporary table. - /// The database transaction within to build the temporary table. - /// The name of the temporary table to build. - /// The values to populate the temporary table with. + /// The database connection to use to build the table. + /// The database transaction within to build the table. + /// The name of the table to build. + /// The values with which to populate the table. /// The type of values in . /// A token that can be used to cancel the operation. /// @@ -65,13 +65,13 @@ public interface ITemporaryTableBuilder /// /// If the type is a scalar type /// (e.g. , , , and so on), - /// a single-column temporary table will be built with a column named "Value" with a data type that matches the + /// a single-column table will be built with a column named "Value" with a data type that matches the /// type . /// /// /// If the type is a complex type (e.g. a class or a record), a multi-column - /// temporary table will be built. - /// The temporary table will contain a column for each instance property (with a public getter) of the type + /// table will be built. + /// The table will contain a column for each instance property (with a public getter) of the type /// . /// The name of each column will be the name of the corresponding property. /// The data type of each column will match the property type of the corresponding property. @@ -89,10 +89,10 @@ public TemporaryTableDisposer BuildTemporaryTable( /// /// Asynchronously builds a temporary table and populates it with the specified values. /// - /// The database connection to use to build the temporary table. - /// The database transaction within to build the temporary table. - /// The name of the temporary table to build. - /// The values to populate the temporary table with. + /// The database connection to use to build the table. + /// The database transaction within to build the table. + /// The name of the table to build. + /// The values with which to populate the table. /// The type of values in . /// A token that can be used to cancel the operation. /// @@ -145,13 +145,13 @@ public TemporaryTableDisposer BuildTemporaryTable( /// /// If the type is a scalar type /// (e.g. , , , and so on), - /// a single-column temporary table will be built with a column named "Value" with a data type that matches + /// a single-column table will be built with a column named "Value" with a data type that matches /// the type . /// /// /// If the type is a complex type (e.g. a class or a record), a multi-column - /// temporary table will be built. - /// The temporary table will contain a column for each instance property (with a public getter) of the type + /// table will be built. + /// The table will contain a column for each instance property (with a public getter) of the type /// . /// The name of each column will be the name of the corresponding property. /// The data type of each column will match the property type of the corresponding property. diff --git a/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlEntityManipulator.cs b/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlEntityManipulator.cs index 732b0bc..18159dd 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlEntityManipulator.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlEntityManipulator.cs @@ -85,7 +85,7 @@ CancellationToken cancellationToken var onClause = String.Join( " AND ", entityTypeMetadata.KeyProperties - .Select(p => $"TKeys.`{p.PropertyName}` = `{entityTypeMetadata.TableName}`.`{p.PropertyName}`") + .Select(p => $"TKeys.`{p.PropertyName}` = `{entityTypeMetadata.TableName}`.`{p.ColumnName}`") ); try @@ -196,7 +196,7 @@ CancellationToken cancellationToken var onClause = String.Join( " AND ", entityTypeMetadata.KeyProperties - .Select(p => $"TKeys.`{p.PropertyName}` = `{entityTypeMetadata.TableName}`.`{p.PropertyName}`") + .Select(p => $"TKeys.`{p.PropertyName}` = `{entityTypeMetadata.TableName}`.`{p.ColumnName}`") ); try @@ -763,11 +763,7 @@ CancellationToken cancellationToken ) { connection.ExecuteNonQuery( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"`{keysTableName}`", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ); @@ -827,11 +823,7 @@ CancellationToken cancellationToken ) { await connection.ExecuteNonQueryAsync( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"`{keysTableName}`", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ).ConfigureAwait(false); @@ -982,53 +974,56 @@ EntityTypeMetadata entityTypeMetadata } /// - /// Gets the SQL code to create a temporary table for the keys of the provided entity type. + /// Creates the SQL code to create a temporary table for the keys of the provided entity type. /// - /// The metadata for the entity type to create the temporary table for. + /// The name of the table to create. + /// The metadata for the entity type to create the table for. /// The SQL code to create the temporary table. - private String GetCreateEntityKeysTemporaryTableSqlCode(EntityTypeMetadata entityTypeMetadata) => - this.createEntityKeysTemporaryTableSqlCodePerEntityType.GetOrAdd( - entityTypeMetadata.EntityType, - _ => - { - if (entityTypeMetadata.KeyProperties.Count == 0) - { - ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); - } + private String CreateEntityKeysTemporaryTableSqlCode( + String tableName, + EntityTypeMetadata entityTypeMetadata + ) + { + if (entityTypeMetadata.KeyProperties.Count == 0) + { + ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); + } - using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); + using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); - createKeysTableSqlBuilder.AppendLine("CREATE TEMPORARY TABLE ###__________EntityKeys__________###"); - createKeysTableSqlBuilder.Append(Constants.Indent); - createKeysTableSqlBuilder.Append("("); + createKeysTableSqlBuilder.Append("CREATE TEMPORARY TABLE `"); + createKeysTableSqlBuilder.Append(tableName); + createKeysTableSqlBuilder.AppendLine("`"); - var prependSeparator = false; + createKeysTableSqlBuilder.Append(Constants.Indent); + createKeysTableSqlBuilder.Append("("); - foreach (var property in entityTypeMetadata.KeyProperties) - { - if (prependSeparator) - { - createKeysTableSqlBuilder.Append(", "); - } + var prependSeparator = false; - createKeysTableSqlBuilder.Append('`'); - createKeysTableSqlBuilder.Append(property.PropertyName); - createKeysTableSqlBuilder.Append("` "); - createKeysTableSqlBuilder.Append( - this.databaseAdapter.GetDataType( - property.PropertyType, - DbConnectionExtensions.EnumSerializationMode - ) - ); + foreach (var property in entityTypeMetadata.KeyProperties) + { + if (prependSeparator) + { + createKeysTableSqlBuilder.Append(", "); + } - prependSeparator = true; - } + createKeysTableSqlBuilder.Append('`'); + createKeysTableSqlBuilder.Append(property.PropertyName); + createKeysTableSqlBuilder.Append("` "); + createKeysTableSqlBuilder.Append( + this.databaseAdapter.GetDataType( + property.PropertyType, + DbConnectionExtensions.EnumSerializationMode + ) + ); - createKeysTableSqlBuilder.AppendLine(")"); + prependSeparator = true; + } - return createKeysTableSqlBuilder.ToString(); - } - ); + createKeysTableSqlBuilder.AppendLine(")"); + + return createKeysTableSqlBuilder.ToString(); + } /// /// Gets the SQL code to delete an entity of the provided entity type. @@ -1067,7 +1062,7 @@ private String GetDeleteEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("` = @"); sqlBuilder.Append(keyProperty.PropertyName); @@ -1109,7 +1104,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('`'); prependSeparator = true; @@ -1158,7 +1153,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('`'); prependSeparator = true; @@ -1184,7 +1179,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => if (identityProperty is not null) { sqlBuilder.Append('`'); - sqlBuilder.Append(identityProperty.PropertyName); + sqlBuilder.Append(identityProperty.ColumnName); sqlBuilder.Append("` = LAST_INSERT_ID()"); } else @@ -1199,7 +1194,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("` = @"); sqlBuilder.Append(keyProperty.PropertyName); prependSeparator = true; @@ -1253,7 +1248,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("` = @"); sqlBuilder.Append(property.PropertyName); @@ -1276,7 +1271,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("` = "); sqlBuilder.Append('@'); sqlBuilder.Append(property.PropertyName); @@ -1303,7 +1298,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('`'); prependSeparator = true; } @@ -1331,7 +1326,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('`'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("` = @"); sqlBuilder.Append(keyProperty.PropertyName); prependSeparator = true; @@ -1440,7 +1435,6 @@ await reader.ReadAsync(cancellationToken).ConfigureAwait(false) } } - private readonly ConcurrentDictionary createEntityKeysTemporaryTableSqlCodePerEntityType = new(); private readonly MySqlDatabaseAdapter databaseAdapter; private readonly ConcurrentDictionary entityDeleteSqlCodePerEntityType = new(); private readonly ConcurrentDictionary entityInsertSqlCodePerEntityType = new(); diff --git a/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlTemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlTemporaryTableBuilder.cs index 7da5a06..6eaa071 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlTemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/MySql/MySqlTemporaryTableBuilder.cs @@ -107,10 +107,21 @@ public TemporaryTableDisposer BuildTemporaryTable( mySqlBulkCopy.ColumnMappings.Clear(); - for (var fieldOrdinal = 0; fieldOrdinal < reader.FieldCount; fieldOrdinal++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) + { + mySqlBulkCopy.ColumnMappings.Add(new(0, Constants.SingleColumnTemporaryTableColumnName)); + } + else { - var fieldName = reader.GetName(fieldOrdinal); - mySqlBulkCopy.ColumnMappings.Add(new(fieldOrdinal, fieldName)); + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead) + .ToList(); + + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + + mySqlBulkCopy.ColumnMappings.Add(new(i, property.ColumnName)); + } } mySqlBulkCopy.WriteToServer(reader); @@ -206,10 +217,21 @@ public async Task BuildTemporaryTableAsync( mySqlBulkCopy.ColumnMappings.Clear(); - for (var fieldOrdinal = 0; fieldOrdinal < reader.FieldCount; fieldOrdinal++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) + { + mySqlBulkCopy.ColumnMappings.Add(new(0, Constants.SingleColumnTemporaryTableColumnName)); + } + else { - var fieldName = reader.GetName(fieldOrdinal); - mySqlBulkCopy.ColumnMappings.Add(new(fieldOrdinal, fieldName)); + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead) + .ToList(); + + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + + mySqlBulkCopy.ColumnMappings.Add(new(i, property.ColumnName)); + } } try @@ -232,8 +254,8 @@ public async Task BuildTemporaryTableAsync( /// Builds an SQL code to create a multi-column temporary table to be populated with objects of the type /// . /// - /// The name of the temporary table to create. - /// The type of objects the temporary table will be populated with. + /// The name of the table to create. + /// The type of objects with which to populate the table. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateMultiColumnTemporaryTableSqlCode( @@ -263,7 +285,7 @@ EnumSerializationMode enumSerializationMode } sqlBuilder.Append("`"); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("` "); var propertyType = property.PropertyType; @@ -282,8 +304,8 @@ EnumSerializationMode enumSerializationMode /// Builds an SQL code to create a single-column temporary table to be populated with values of the type /// . /// - /// The name of the temporary table to create. - /// The type of values the temporary table will be populated with. + /// The name of the table to create. + /// The type of values with which the table will be populated. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateSingleColumnTemporaryTableSqlCode( @@ -299,7 +321,9 @@ EnumSerializationMode enumSerializationMode sqlBuilder.AppendLine("`"); sqlBuilder.Append(Constants.Indent); - sqlBuilder.Append("(`Value` "); + sqlBuilder.Append("(`"); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("` "); sqlBuilder.Append(this.databaseAdapter.GetDataType(valuesType, enumSerializationMode)); sqlBuilder.AppendLine(")"); @@ -350,10 +374,10 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu ) }; - return new EnumerableReader(enumValues, newValuesType, "Value"); + return new EnumerableReader(enumValues, newValuesType, Constants.SingleColumnTemporaryTableColumnName); } - return new EnumerableReader(values, valuesType, "Value"); + return new EnumerableReader(values, valuesType, Constants.SingleColumnTemporaryTableColumnName); } return new EnumHandlingObjectReader( @@ -368,7 +392,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu /// /// Drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. private static void DropTemporaryTable(String name, MySqlConnection connection, MySqlTransaction? transaction) @@ -387,7 +411,7 @@ private static void DropTemporaryTable(String name, MySqlConnection connection, /// /// Asynchronously drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. /// A task representing the asynchronous operation. diff --git a/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleEntityManipulator.cs b/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleEntityManipulator.cs index 3c86c13..ae443e2 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleEntityManipulator.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleEntityManipulator.cs @@ -637,7 +637,7 @@ EntityTypeMetadata entityTypeMetadata foreach (var property in entityTypeMetadata.DatabaseGeneratedProperties) { var parameter = command.CreateParameter(); - parameter.ParameterName = "return_" + property.PropertyName; + parameter.ParameterName = "return_" + property.ColumnName; parameter.DbType = this.databaseAdapter.GetDbType( property.PropertyType, DbConnectionExtensions.EnumSerializationMode @@ -688,7 +688,7 @@ EntityTypeMetadata entityTypeMetadata foreach (var property in entityTypeMetadata.DatabaseGeneratedProperties) { var parameter = command.CreateParameter(); - parameter.ParameterName = "return_" + property.PropertyName; + parameter.ParameterName = "return_" + property.ColumnName; parameter.DbType = this.databaseAdapter.GetDbType( property.PropertyType, DbConnectionExtensions.EnumSerializationMode @@ -739,7 +739,7 @@ private String GetDeleteEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("\" =:\""); sqlBuilder.Append(keyProperty.PropertyName); sqlBuilder.Append('"'); @@ -781,7 +781,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; @@ -826,7 +826,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -847,7 +847,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append(":\"return_"); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -895,7 +895,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" = :\""); sqlBuilder.Append(property.PropertyName); sqlBuilder.Append('"'); @@ -919,7 +919,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" = "); sqlBuilder.Append(":\""); sqlBuilder.Append(property.PropertyName); @@ -946,7 +946,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -967,7 +967,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append(":\"return_"); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } diff --git a/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleTemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleTemporaryTableBuilder.cs index a6745a1..dc2ae73 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleTemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/Oracle/OracleTemporaryTableBuilder.cs @@ -111,7 +111,7 @@ public TemporaryTableDisposer BuildTemporaryTable( // ReSharper disable once PossibleMultipleEnumeration using var reader = CreateValuesDataReader(values, valuesType); - this.PopulateTemporaryTable(oracleConnection, oracleTransaction, quotedTableName, reader, cancellationToken); + this.PopulateTemporaryTable(oracleConnection, oracleTransaction, quotedTableName, valuesType, reader, cancellationToken); return new( () => DropTemporaryTable(quotedTableName, oracleConnection, oracleTransaction), @@ -210,6 +210,7 @@ await this.PopulateTemporaryTableAsync( oracleConnection, oracleTransaction, quotedTableName, + valuesType, reader, cancellationToken ) @@ -225,8 +226,8 @@ await this.PopulateTemporaryTableAsync( /// Builds an SQL code to create a multi-column temporary table to be populated with objects of the type /// . /// - /// The quoted name of the temporary table to create. - /// The type of objects the temporary table will be populated with. + /// The quoted name of the table to create. + /// The type of objects with which to populate the table. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateMultiColumnTemporaryTableSqlCode( @@ -255,7 +256,7 @@ EnumSerializationMode enumSerializationMode } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" "); var propertyType = property.PropertyType; @@ -274,9 +275,9 @@ EnumSerializationMode enumSerializationMode /// Builds an SQL code to create a single-column temporary table to be populated with values of the type /// . /// - /// The quoted name of the temporary table to create. - /// The values to populate the temporary table with. - /// The type of values the temporary table will be populated with. + /// The quoted name of the table to create. + /// The values with which to populate the table. + /// The type of values with which the table will be populated. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateSingleColumnTemporaryTableSqlCode( @@ -292,7 +293,9 @@ EnumSerializationMode enumSerializationMode sqlBuilder.AppendLine(quotedTableName); sqlBuilder.Append(Constants.Indent); - sqlBuilder.Append("(\"Value\" "); + sqlBuilder.Append("(\""); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("\" "); if (valuesType == typeof(String)) { @@ -336,15 +339,17 @@ EnumSerializationMode enumSerializationMode /// /// Populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The database transaction within to populate the temporary table. - /// The quoted name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The database transaction within to populate the table. + /// The quoted name of the table to populate. + /// The type of values with which to populate the table. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. private void PopulateTemporaryTable( OracleConnection connection, OracleTransaction? transaction, String quotedTableName, + Type valuesType, DbDataReader dataReader, CancellationToken cancellationToken ) @@ -352,7 +357,7 @@ CancellationToken cancellationToken var insertCommand = connection.CreateCommand(); insertCommand.Transaction = transaction; - var (insertSqlCode, parameters) = BuildInsertSqlCode(quotedTableName, dataReader); + var (insertSqlCode, parameters) = BuildInsertSqlCode(quotedTableName, valuesType, dataReader); #pragma warning disable CA2100 insertCommand.CommandText = insertSqlCode; @@ -381,16 +386,18 @@ CancellationToken cancellationToken /// /// Asynchronously populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The database transaction within to populate the temporary table. - /// The quoted name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The database transaction within to populate the table. + /// The quoted name of the table to populate. + /// The type of values with which to populate the table. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. /// A task that represents the asynchronous operation. private async Task PopulateTemporaryTableAsync( OracleConnection connection, OracleTransaction? transaction, String quotedTableName, + Type valuesType, DbDataReader dataReader, CancellationToken cancellationToken ) @@ -401,7 +408,7 @@ CancellationToken cancellationToken insertCommand.Transaction = transaction; - var (insertSqlCode, parameters) = BuildInsertSqlCode(quotedTableName, dataReader); + var (insertSqlCode, parameters) = BuildInsertSqlCode(quotedTableName, valuesType, dataReader); #pragma warning disable CA2100 insertCommand.CommandText = insertSqlCode; @@ -428,11 +435,13 @@ CancellationToken cancellationToken /// /// Builds an SQL code to insert data from the specified data reader into the specified temporary table. /// - /// The quoted name of the temporary table to insert data into. + /// The quoted name of the table to insert data into. + /// The type of values with which to populate the table. /// The data reader to read data from. /// A tuple containing the insert SQL code and the parameters to use. private static (String SqlCode, OracleParameter[] Parameters) BuildInsertSqlCode( String quotedTableName, + Type valuesType, DbDataReader dataReader ) { @@ -447,23 +456,39 @@ DbDataReader dataReader var fieldCount = dataReader.FieldCount; var parameters = new OracleParameter[fieldCount]; - for (var i = 0; i < fieldCount; i++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - if (i > 0) + sqlBuilder.Append("\""); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("\""); + + parameters[0] = new() { - sqlBuilder.Append(", "); - } + ParameterName = Constants.SingleColumnTemporaryTableColumnName + }; + } + else + { + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead).ToList(); - var fieldName = dataReader.GetName(i); + for (var i = 0; i < properties.Count; i++) + { + if (i > 0) + { + sqlBuilder.Append(", "); + } - sqlBuilder.Append('"'); - sqlBuilder.Append(fieldName); - sqlBuilder.Append('"'); + var property = properties[i]; - parameters[i] = new() - { - ParameterName = fieldName - }; + sqlBuilder.Append('"'); + sqlBuilder.Append(property.ColumnName); + sqlBuilder.Append('"'); + + parameters[i] = new() + { + ParameterName = property.PropertyName + }; + } } sqlBuilder.AppendLine(")"); @@ -498,7 +523,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu { if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - return new EnumerableReader(values, valuesType, "Value"); + return new EnumerableReader(values, valuesType, Constants.SingleColumnTemporaryTableColumnName); } return new ObjectReader( @@ -514,7 +539,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu /// /// Drops the temporary table with the specified name. /// - /// The quoted name of the temporary table to drop. + /// The quoted name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. private static void DropTemporaryTable( @@ -537,7 +562,7 @@ private static void DropTemporaryTable( /// /// Asynchronously drops the temporary table with the specified name. /// - /// The quoted name of the temporary table to drop. + /// The quoted name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. /// A task representing the asynchronous operation. diff --git a/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlEntityManipulator.cs b/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlEntityManipulator.cs index e51ed6e..c7c0bc3 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlEntityManipulator.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlEntityManipulator.cs @@ -83,7 +83,7 @@ CancellationToken cancellationToken var whereClause = String.Join( " AND ", entityTypeMetadata.KeyProperties.Select(p => - $"TKeys.\"{p.PropertyName}\" = \"{entityTypeMetadata.TableName}\".\"{p.PropertyName}\"" + $"TKeys.\"{p.PropertyName}\" = \"{entityTypeMetadata.TableName}\".\"{p.ColumnName}\"" ) ); @@ -193,7 +193,7 @@ CancellationToken cancellationToken var whereClause = String.Join( " AND ", entityTypeMetadata.KeyProperties.Select(p => - $"TKeys.\"{p.PropertyName}\" = \"{entityTypeMetadata.TableName}\".\"{p.PropertyName}\"" + $"TKeys.\"{p.PropertyName}\" = \"{entityTypeMetadata.TableName}\".\"{p.ColumnName}\"" ) ); @@ -764,11 +764,7 @@ CancellationToken cancellationToken ) { connection.ExecuteNonQuery( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"\"{keysTableName}\"", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ); @@ -832,11 +828,7 @@ CancellationToken cancellationToken ) { await connection.ExecuteNonQueryAsync( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"\"{keysTableName}\"", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ).ConfigureAwait(false); @@ -996,54 +988,56 @@ EntityTypeMetadata entityTypeMetadata } /// - /// Gets the SQL code to create a temporary table for the keys of the provided entity type. + /// Creates the SQL code to create a temporary table for the keys of the provided entity type. /// - /// The metadata for the entity type to create the temporary table for. + /// The name of the table to create. + /// The metadata for the entity type to create the table for. /// The SQL code to create the temporary table. - private String GetCreateEntityKeysTemporaryTableSqlCode(EntityTypeMetadata entityTypeMetadata) => - this.createEntityKeysTemporaryTableSqlCodePerEntityType.GetOrAdd( - entityTypeMetadata.EntityType, - _ => - { - if (entityTypeMetadata.KeyProperties.Count == 0) - { - ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); - } + private String CreateEntityKeysTemporaryTableSqlCode( + String tableName, + EntityTypeMetadata entityTypeMetadata + ) + { + if (entityTypeMetadata.KeyProperties.Count == 0) + { + ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); + } - using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); + using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); - createKeysTableSqlBuilder.AppendLine("CREATE TEMP TABLE ###__________EntityKeys__________###"); + createKeysTableSqlBuilder.Append("CREATE TEMP TABLE \""); + createKeysTableSqlBuilder.Append(tableName); + createKeysTableSqlBuilder.AppendLine("\""); - createKeysTableSqlBuilder.Append(Constants.Indent); - createKeysTableSqlBuilder.Append("("); + createKeysTableSqlBuilder.Append(Constants.Indent); + createKeysTableSqlBuilder.Append("("); - var prependSeparator = false; + var prependSeparator = false; - foreach (var property in entityTypeMetadata.KeyProperties) - { - if (prependSeparator) - { - createKeysTableSqlBuilder.Append(", "); - } + foreach (var property in entityTypeMetadata.KeyProperties) + { + if (prependSeparator) + { + createKeysTableSqlBuilder.Append(", "); + } - createKeysTableSqlBuilder.Append('"'); - createKeysTableSqlBuilder.Append(property.PropertyName); - createKeysTableSqlBuilder.Append("\" "); - createKeysTableSqlBuilder.Append( - this.databaseAdapter.GetDataType( - property.PropertyType, - DbConnectionExtensions.EnumSerializationMode - ) - ); + createKeysTableSqlBuilder.Append('"'); + createKeysTableSqlBuilder.Append(property.PropertyName); + createKeysTableSqlBuilder.Append("\" "); + createKeysTableSqlBuilder.Append( + this.databaseAdapter.GetDataType( + property.PropertyType, + DbConnectionExtensions.EnumSerializationMode + ) + ); - prependSeparator = true; - } + prependSeparator = true; + } - createKeysTableSqlBuilder.AppendLine(")"); + createKeysTableSqlBuilder.AppendLine(")"); - return createKeysTableSqlBuilder.ToString(); - } - ); + return createKeysTableSqlBuilder.ToString(); + } /// /// Gets the SQL code to delete an entity of the provided entity type. @@ -1083,7 +1077,7 @@ private String GetDeleteEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("\" = @"); sqlBuilder.Append(keyProperty.PropertyName); @@ -1125,7 +1119,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; @@ -1171,7 +1165,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -1221,7 +1215,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" = @"); sqlBuilder.Append(property.PropertyName); @@ -1244,7 +1238,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" = "); sqlBuilder.Append('@'); sqlBuilder.Append(property.PropertyName); @@ -1270,7 +1264,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -1376,7 +1370,6 @@ await reader.ReadAsync(cancellationToken).ConfigureAwait(false) } } - private readonly ConcurrentDictionary createEntityKeysTemporaryTableSqlCodePerEntityType = new(); private readonly PostgreSqlDatabaseAdapter databaseAdapter; private readonly ConcurrentDictionary entityDeleteSqlCodePerEntityType = new(); private readonly ConcurrentDictionary entityInsertSqlCodePerEntityType = new(); diff --git a/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlTemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlTemporaryTableBuilder.cs index 51f4719..d8f572a 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlTemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/PostgreSql/PostgreSqlTemporaryTableBuilder.cs @@ -194,8 +194,8 @@ await this.PopulateTemporaryTableAsync(npgsqlConnection, name, reader, cancellat /// Builds an SQL code to create a multi-column temporary table to be populated with objects of the type /// . /// - /// The name of the temporary table to create. - /// The type of objects the temporary table will be populated with. + /// The name of the table to create. + /// The type of objects with which to populate the table. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateMultiColumnTemporaryTableSqlCode( @@ -225,7 +225,7 @@ EnumSerializationMode enumSerializationMode } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" "); var propertyType = property.PropertyType; @@ -244,8 +244,8 @@ EnumSerializationMode enumSerializationMode /// Builds an SQL code to create a single-column temporary table to be populated with values of the type /// . /// - /// The name of the temporary table to create. - /// The type of values the temporary table will be populated with. + /// The name of the table to create. + /// The type of values with which the table will be populated. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateSingleColumnTemporaryTableSqlCode( @@ -261,7 +261,9 @@ EnumSerializationMode enumSerializationMode sqlBuilder.AppendLine("\""); sqlBuilder.Append(Constants.Indent); - sqlBuilder.Append("(\"Value\" "); + sqlBuilder.Append("(\""); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("\" "); sqlBuilder.Append(this.databaseAdapter.GetDataType(valuesType, enumSerializationMode)); sqlBuilder.AppendLine(")"); @@ -271,9 +273,9 @@ EnumSerializationMode enumSerializationMode /// /// Populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The name of the table to populate. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. private void PopulateTemporaryTable( NpgsqlConnection connection, @@ -322,9 +324,9 @@ CancellationToken cancellationToken /// /// Asynchronously populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The name of the table to populate. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. /// A task that represents the asynchronous operation. private async Task PopulateTemporaryTableAsync( @@ -387,7 +389,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu { if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - return new EnumerableReader(values, valuesType, "Value"); + return new EnumerableReader(values, valuesType, Constants.SingleColumnTemporaryTableColumnName); } return new ObjectReader( @@ -402,7 +404,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu /// /// Drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. private static void DropTemporaryTable(String name, NpgsqlConnection connection, NpgsqlTransaction? transaction) @@ -421,7 +423,7 @@ private static void DropTemporaryTable(String name, NpgsqlConnection connection, /// /// Asynchronously drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. /// A task representing the asynchronous operation. diff --git a/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerEntityManipulator.cs b/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerEntityManipulator.cs index 841f2fc..5f6eab2 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerEntityManipulator.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerEntityManipulator.cs @@ -81,7 +81,7 @@ CancellationToken cancellationToken var onClause = String.Join( " AND ", - entityTypeMetadata.KeyProperties.Select(p => $"TKeys.[{p.PropertyName}] = TEntities.[{p.PropertyName}]") + entityTypeMetadata.KeyProperties.Select(p => $"TKeys.[{p.PropertyName}] = TEntities.[{p.ColumnName}]") ); try @@ -191,7 +191,7 @@ CancellationToken cancellationToken var onClause = String.Join( " AND ", - entityTypeMetadata.KeyProperties.Select(p => $"TKeys.[{p.PropertyName}] = TEntities.[{p.PropertyName}]") + entityTypeMetadata.KeyProperties.Select(p => $"TKeys.[{p.PropertyName}] = TEntities.[{p.ColumnName}]") ); try @@ -760,11 +760,7 @@ CancellationToken cancellationToken ) { connection.ExecuteNonQuery( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"[#{keysTableName}]", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ); @@ -821,11 +817,7 @@ CancellationToken cancellationToken ) { await connection.ExecuteNonQueryAsync( - this.GetCreateEntityKeysTemporaryTableSqlCode(entityTypeMetadata).Replace( - "###__________EntityKeys__________###", - $"[#{keysTableName}]", - StringComparison.Ordinal - ), + this.CreateEntityKeysTemporaryTableSqlCode(keysTableName, entityTypeMetadata), transaction, cancellationToken: cancellationToken ).ConfigureAwait(false); @@ -973,54 +965,56 @@ EntityTypeMetadata entityTypeMetadata } /// - /// Gets the SQL code to create a temporary table for the keys of the provided entity type. + /// Creates the SQL code to create a temporary table for the keys of the provided entity type. /// - /// The metadata for the entity type to create the temporary table for. + /// The name of the table to create. + /// The metadata for the entity type to create the table for. /// The SQL code to create the temporary table. - private String GetCreateEntityKeysTemporaryTableSqlCode(EntityTypeMetadata entityTypeMetadata) => - this.createEntityKeysTemporaryTableSqlCodePerEntityType.GetOrAdd( - entityTypeMetadata.EntityType, - _ => - { - if (entityTypeMetadata.KeyProperties.Count == 0) - { - ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); - } + private String CreateEntityKeysTemporaryTableSqlCode( + String tableName, + EntityTypeMetadata entityTypeMetadata + ) + { + if (entityTypeMetadata.KeyProperties.Count == 0) + { + ThrowHelper.ThrowEntityTypeHasNoKeyPropertyException(entityTypeMetadata.EntityType); + } - using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); + using var createKeysTableSqlBuilder = new ValueStringBuilder(stackalloc Char[200]); - createKeysTableSqlBuilder.AppendLine("CREATE TABLE ###__________EntityKeys__________###"); + createKeysTableSqlBuilder.Append("CREATE TABLE [#"); + createKeysTableSqlBuilder.Append(tableName); + createKeysTableSqlBuilder.AppendLine("]"); - createKeysTableSqlBuilder.Append(Constants.Indent); - createKeysTableSqlBuilder.Append("("); + createKeysTableSqlBuilder.Append(Constants.Indent); + createKeysTableSqlBuilder.Append("("); - var prependSeparator = false; + var prependSeparator = false; - foreach (var property in entityTypeMetadata.KeyProperties) - { - if (prependSeparator) - { - createKeysTableSqlBuilder.Append(", "); - } + foreach (var property in entityTypeMetadata.KeyProperties) + { + if (prependSeparator) + { + createKeysTableSqlBuilder.Append(", "); + } - createKeysTableSqlBuilder.Append('['); - createKeysTableSqlBuilder.Append(property.PropertyName); - createKeysTableSqlBuilder.Append("] "); - createKeysTableSqlBuilder.Append( - this.databaseAdapter.GetDataType( - property.PropertyType, - DbConnectionExtensions.EnumSerializationMode - ) - ); + createKeysTableSqlBuilder.Append('['); + createKeysTableSqlBuilder.Append(property.PropertyName); + createKeysTableSqlBuilder.Append("] "); + createKeysTableSqlBuilder.Append( + this.databaseAdapter.GetDataType( + property.PropertyType, + DbConnectionExtensions.EnumSerializationMode + ) + ); - prependSeparator = true; - } + prependSeparator = true; + } - createKeysTableSqlBuilder.AppendLine(")"); + createKeysTableSqlBuilder.AppendLine(")"); - return createKeysTableSqlBuilder.ToString(); - } - ); + return createKeysTableSqlBuilder.ToString(); + } /// /// Gets the SQL code to delete an entity of the provided entity type. @@ -1060,7 +1054,7 @@ private String GetDeleteEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('['); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("] = @"); sqlBuilder.Append(keyProperty.PropertyName); @@ -1102,7 +1096,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('['); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append(']'); prependSeparator = true; @@ -1126,7 +1120,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append("INSERTED.["); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append(']'); prependSeparator = true; } @@ -1198,7 +1192,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('['); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("] = @"); sqlBuilder.Append(property.PropertyName); @@ -1223,7 +1217,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append("INSERTED.["); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append(']'); prependSeparator = true; } @@ -1245,7 +1239,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('['); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("] = "); sqlBuilder.Append('@'); sqlBuilder.Append(property.PropertyName); @@ -1353,7 +1347,6 @@ await reader.ReadAsync(cancellationToken).ConfigureAwait(false) } } - private readonly ConcurrentDictionary createEntityKeysTemporaryTableSqlCodePerEntityType = new(); private readonly SqlServerDatabaseAdapter databaseAdapter; private readonly ConcurrentDictionary entityDeleteSqlCodePerEntityType = new(); private readonly ConcurrentDictionary entityInsertSqlCodePerEntityType = new(); diff --git a/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerTemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerTemporaryTableBuilder.cs index 44741b1..072788e 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerTemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/SqlServer/SqlServerTemporaryTableBuilder.cs @@ -116,10 +116,18 @@ public TemporaryTableDisposer BuildTemporaryTable( sqlBulkCopy.ColumnMappings.Clear(); - for (var fieldOrdinal = 0; fieldOrdinal < reader.FieldCount; fieldOrdinal++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - var fieldName = reader.GetName(fieldOrdinal); - sqlBulkCopy.ColumnMappings.Add(fieldName, fieldName); + sqlBulkCopy.ColumnMappings.Add(Constants.SingleColumnTemporaryTableColumnName, Constants.SingleColumnTemporaryTableColumnName); + } + else + { + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead); + + foreach (var property in properties) + { + sqlBulkCopy.ColumnMappings.Add(property.PropertyName, property.ColumnName); + } } sqlBulkCopy.WriteToServer(reader); @@ -223,10 +231,18 @@ public async Task BuildTemporaryTableAsync( sqlBulkCopy.ColumnMappings.Clear(); - for (var fieldOrdinal = 0; fieldOrdinal < reader.FieldCount; fieldOrdinal++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - var fieldName = reader.GetName(fieldOrdinal); - sqlBulkCopy.ColumnMappings.Add(fieldName, fieldName); + sqlBulkCopy.ColumnMappings.Add(Constants.SingleColumnTemporaryTableColumnName, Constants.SingleColumnTemporaryTableColumnName); + } + else + { + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead); + + foreach (var property in properties) + { + sqlBulkCopy.ColumnMappings.Add(property.PropertyName, property.ColumnName); + } } try @@ -249,8 +265,8 @@ public async Task BuildTemporaryTableAsync( /// Builds an SQL code to create a multi-column temporary table to be populated with objects of the type /// . /// - /// The name of the temporary table to create. - /// The type of objects the temporary table will be populated with. + /// The name of the table to create. + /// The type of objects with which to populate the table. /// The collation to use for text columns. /// The mode to use to serialize values. /// The built SQL code. @@ -282,7 +298,7 @@ EnumSerializationMode enumSerializationMode } sqlBuilder.Append('['); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("] "); var propertyType = property.PropertyType; @@ -314,9 +330,9 @@ EnumSerializationMode enumSerializationMode /// Builds an SQL code to create a single-column temporary table to be populated with values of the type /// . /// - /// The name of the temporary table to create. - /// The values to populate the temporary table with. - /// The type of values the temporary table will be populated with. + /// The name of the table to create. + /// The values with which to populate the table. + /// The type of values with which the table will be populated. /// The collation to use for text columns. /// The mode to use to serialize values. /// The built SQL code. @@ -335,7 +351,9 @@ EnumSerializationMode enumSerializationMode sqlBuilder.AppendLine("]"); sqlBuilder.Append(Constants.Indent); - sqlBuilder.Append("([Value] "); + sqlBuilder.Append("(["); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("] "); if (valuesType == typeof(String)) { @@ -399,7 +417,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu { if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - return new EnumerableReader(values, valuesType, "Value"); + return new EnumerableReader(values, valuesType, Constants.SingleColumnTemporaryTableColumnName); } return new ObjectReader( @@ -414,7 +432,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu /// /// Drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. private static void DropTemporaryTable(String name, SqlConnection connection, SqlTransaction? transaction) @@ -433,7 +451,7 @@ private static void DropTemporaryTable(String name, SqlConnection connection, Sq /// /// Asynchronously drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. /// A task representing the asynchronous operation. diff --git a/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteEntityManipulator.cs b/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteEntityManipulator.cs index 4a29866..0592aa2 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteEntityManipulator.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteEntityManipulator.cs @@ -722,7 +722,7 @@ private String GetDeleteEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('\"'); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("\" = @"); sqlBuilder.Append(keyProperty.PropertyName); @@ -764,7 +764,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; @@ -813,7 +813,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; @@ -839,7 +839,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => if (identityProperty is not null) { sqlBuilder.Append('"'); - sqlBuilder.Append(identityProperty.PropertyName); + sqlBuilder.Append(identityProperty.ColumnName); sqlBuilder.Append("\" = last_insert_rowid()"); } else @@ -854,7 +854,7 @@ private String GetInsertEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append("\""); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("\" = @"); sqlBuilder.Append(keyProperty.PropertyName); prependSeparator = true; @@ -908,7 +908,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); sqlBuilder.Append(" = @"); sqlBuilder.Append(property.PropertyName); @@ -932,7 +932,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); sqlBuilder.Append(" = "); sqlBuilder.Append('@'); @@ -961,7 +961,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append('"'); prependSeparator = true; } @@ -989,7 +989,7 @@ private String GetUpdateEntitySqlCode(EntityTypeMetadata entityTypeMetadata) => } sqlBuilder.Append("\""); - sqlBuilder.Append(keyProperty.PropertyName); + sqlBuilder.Append(keyProperty.ColumnName); sqlBuilder.Append("\" = @"); sqlBuilder.Append(keyProperty.PropertyName); prependSeparator = true; diff --git a/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteTemporaryTableBuilder.cs b/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteTemporaryTableBuilder.cs index d1a41bf..d412150 100644 --- a/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteTemporaryTableBuilder.cs +++ b/src/DbConnectionPlus/DatabaseAdapters/Sqlite/SqliteTemporaryTableBuilder.cs @@ -99,7 +99,7 @@ public TemporaryTableDisposer BuildTemporaryTable( using var reader = CreateValuesDataReader(values, valuesType); - PopulateTemporaryTable(sqliteConnection, sqliteTransaction, name, reader, cancellationToken); + PopulateTemporaryTable(sqliteConnection, sqliteTransaction, name, valuesType, reader, cancellationToken); return new( () => DropTemporaryTable(name, sqliteConnection, sqliteTransaction), @@ -181,7 +181,7 @@ public async Task BuildTemporaryTableAsync( await using var reader = CreateValuesDataReader(values, valuesType); #pragma warning restore CA2007 - await PopulateTemporaryTableAsync(sqliteConnection, sqliteTransaction, name, reader, cancellationToken) + await PopulateTemporaryTableAsync(sqliteConnection, sqliteTransaction, name, valuesType, reader, cancellationToken) .ConfigureAwait(false); return new( @@ -194,8 +194,8 @@ await PopulateTemporaryTableAsync(sqliteConnection, sqliteTransaction, name, rea /// Builds an SQL code to create a multi-column temporary table to be populated with objects of the type /// . /// - /// The name of the temporary table to create. - /// The type of objects the temporary table will be populated with. + /// The name of the table to create. + /// The type of objects with which to populate the table. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateMultiColumnTemporaryTableSqlCode( @@ -225,7 +225,7 @@ EnumSerializationMode enumSerializationMode } sqlBuilder.Append('"'); - sqlBuilder.Append(property.PropertyName); + sqlBuilder.Append(property.ColumnName); sqlBuilder.Append("\" "); var propertyType = property.PropertyType; @@ -244,8 +244,8 @@ EnumSerializationMode enumSerializationMode /// Builds an SQL code to create a single-column temporary table to be populated with values of the type /// . /// - /// The name of the temporary table to create. - /// The type of values the temporary table will be populated with. + /// The name of the table to create. + /// The type of values with which the table will be populated. /// The mode to use to serialize values. /// The built SQL code. private String BuildCreateSingleColumnTemporaryTableSqlCode( @@ -261,7 +261,9 @@ EnumSerializationMode enumSerializationMode sqlBuilder.AppendLine("\""); sqlBuilder.Append(Constants.Indent); - sqlBuilder.Append("(\"Value\" "); + sqlBuilder.Append("(\""); + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + sqlBuilder.Append("\" "); sqlBuilder.Append(this.databaseAdapter.GetDataType(valuesType, enumSerializationMode)); sqlBuilder.AppendLine(")"); @@ -271,11 +273,13 @@ EnumSerializationMode enumSerializationMode /// /// Builds an SQL code to insert data from the specified data reader into the specified temporary table. /// - /// The name of the temporary table to insert data into. + /// The name of the table to insert data into. + /// The type of values with which to populate the table. /// The data reader to read data from. /// A tuple containing the insert SQL code and the parameters to use. private static (String SqlCode, SqliteParameter[] Parameters) BuildInsertSqlCode( String tableName, + Type valuesType, DbDataReader dataReader ) { @@ -291,23 +295,37 @@ DbDataReader dataReader var fieldCount = dataReader.FieldCount; var parameters = new SqliteParameter[fieldCount]; - for (var i = 0; i < fieldCount; i++) + if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - if (i > 0) + sqlBuilder.Append(Constants.SingleColumnTemporaryTableColumnName); + + parameters[0] = new() { - sqlBuilder.Append(", "); - } + ParameterName = Constants.SingleColumnTemporaryTableColumnName + }; + } + else + { + var properties = EntityHelper.GetEntityTypeMetadata(valuesType).MappedProperties.Where(a => a.CanRead).ToList(); - var fieldName = dataReader.GetName(i); + for (var i = 0; i < properties.Count; i++) + { + if (i > 0) + { + sqlBuilder.Append(", "); + } - sqlBuilder.Append('"'); - sqlBuilder.Append(fieldName); - sqlBuilder.Append('"'); + var property = properties[i]; - parameters[i] = new() - { - ParameterName = "@" + fieldName - }; + sqlBuilder.Append('"'); + sqlBuilder.Append(property.ColumnName); + sqlBuilder.Append('"'); + + parameters[i] = new() + { + ParameterName = property.PropertyName + }; + } } sqlBuilder.AppendLine(")"); @@ -324,6 +342,7 @@ DbDataReader dataReader sqlBuilder.Append(", "); } + sqlBuilder.Append("@"); sqlBuilder.Append(parameters[i].ParameterName); } @@ -342,7 +361,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu { if (valuesType.IsBuiltInTypeOrNullableBuiltInType() || valuesType.IsEnumOrNullableEnumType()) { - return new EnumerableReader(values, valuesType, "Value"); + return new EnumerableReader(values, valuesType, Constants.SingleColumnTemporaryTableColumnName); } return new ObjectReader( @@ -357,7 +376,7 @@ private static DbDataReader CreateValuesDataReader(IEnumerable values, Type valu /// /// Drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. private static void DropTemporaryTable(String name, SqliteConnection connection, SqliteTransaction? transaction) @@ -376,7 +395,7 @@ private static void DropTemporaryTable(String name, SqliteConnection connection, /// /// Asynchronously drops the temporary table with the specified name. /// - /// The name of the temporary table to drop. + /// The name of the table to drop. /// The connection to use to drop the table. /// The transaction within to drop the table. /// A task representing the asynchronous operation. @@ -402,15 +421,17 @@ private static async ValueTask DropTemporaryTableAsync( /// /// Populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The database transaction within to populate the temporary table. - /// The name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The database transaction within to populate the table. + /// The name of the table to populate. + /// The type of values with which to populate the table. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. private static void PopulateTemporaryTable( SqliteConnection connection, SqliteTransaction? transaction, String tableName, + Type valuesType, DbDataReader dataReader, CancellationToken cancellationToken ) @@ -418,7 +439,7 @@ CancellationToken cancellationToken var insertCommand = connection.CreateCommand(); insertCommand.Transaction = transaction; - var (insertSqlCode, parameters) = BuildInsertSqlCode(tableName, dataReader); + var (insertSqlCode, parameters) = BuildInsertSqlCode(tableName, valuesType, dataReader); #pragma warning disable CA2100 insertCommand.CommandText = insertSqlCode; @@ -451,16 +472,18 @@ CancellationToken cancellationToken /// /// Asynchronously populates the specified temporary table with the data from the specified data reader. /// - /// The database connection to use to populate the temporary table. - /// The database transaction within to populate the temporary table. - /// The name of the temporary table to populate. - /// The data reader to use to populate the temporary table. + /// The database connection to use to populate the table. + /// The database transaction within to populate the table. + /// The name of the table to populate. + /// The type of values with which to populate the table. + /// The data reader to use to populate the table. /// A token that can be used to cancel the operation. /// A task that represents the asynchronous operation. private static async Task PopulateTemporaryTableAsync( SqliteConnection connection, SqliteTransaction? transaction, String tableName, + Type valuesType, DbDataReader dataReader, CancellationToken cancellationToken ) @@ -471,7 +494,7 @@ CancellationToken cancellationToken insertCommand.Transaction = transaction; - var (insertSqlCode, parameters) = BuildInsertSqlCode(tableName, dataReader); + var (insertSqlCode, parameters) = BuildInsertSqlCode(tableName, valuesType, dataReader); #pragma warning disable CA2100 insertCommand.CommandText = insertSqlCode; diff --git a/src/DbConnectionPlus/DbCommands/DbCommandBuilder.cs b/src/DbConnectionPlus/DbCommands/DbCommandBuilder.cs index 2e8e3b1..3fdf9fa 100644 --- a/src/DbConnectionPlus/DbCommands/DbCommandBuilder.cs +++ b/src/DbConnectionPlus/DbCommands/DbCommandBuilder.cs @@ -293,14 +293,14 @@ private static (DbCommand, InterpolatedTemporaryTable[], CancellationTokenRegist /// /// Builds the specified temporary tables. /// - /// The temporary tables to build. - /// The database adapter to use to build the temporary tables. - /// The database connection to use to build the temporary tables. - /// The database transaction within to build the temporary tables. + /// The tables to build. + /// The database adapter to use to build the tables. + /// The database connection to use to build the tables. + /// The database transaction within to build the tables. /// A token that can be used to cancel the operation. /// /// An array of instances that can be used to dispose the built - /// temporary tables. + /// tables. /// /// /// does not support (local / session-scoped) temporary tables. @@ -357,15 +357,15 @@ CancellationToken cancellationToken /// /// Asynchronously builds the specified temporary tables. /// - /// The temporary tables to build. - /// The database adapter to use to build the temporary tables. - /// The database connection to use to build the temporary tables. - /// The database transaction within to build the temporary tables. + /// The tables to build. + /// The database adapter to use to build the tables. + /// The database connection to use to build the tables. + /// The database transaction within to build the tables. /// A token that can be used to cancel the operation. /// /// A task that represents the asynchronous operation. /// will contain an array of - /// instances that can be used to dispose the built temporary tables. + /// instances that can be used to dispose the built tables. /// /// /// does not support (local / session-scoped) temporary tables. diff --git a/src/DbConnectionPlus/DbConnectionExtensions.InsertEntities.cs b/src/DbConnectionPlus/DbConnectionExtensions.InsertEntities.cs index 639ba1c..5938987 100644 --- a/src/DbConnectionPlus/DbConnectionExtensions.InsertEntities.cs +++ b/src/DbConnectionPlus/DbConnectionExtensions.InsertEntities.cs @@ -45,6 +45,8 @@ public static partial class DbConnectionExtensions /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. @@ -137,6 +139,8 @@ public static Int32 InsertEntities( /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. diff --git a/src/DbConnectionPlus/DbConnectionExtensions.InsertEntity.cs b/src/DbConnectionPlus/DbConnectionExtensions.InsertEntity.cs index 789b205..e1424d2 100644 --- a/src/DbConnectionPlus/DbConnectionExtensions.InsertEntity.cs +++ b/src/DbConnectionPlus/DbConnectionExtensions.InsertEntity.cs @@ -45,6 +45,8 @@ public static partial class DbConnectionExtensions /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. @@ -137,6 +139,8 @@ public static Int32 InsertEntity( /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. diff --git a/src/DbConnectionPlus/DbConnectionExtensions.TemporaryTable.cs b/src/DbConnectionPlus/DbConnectionExtensions.TemporaryTable.cs index 1700ff3..8879835 100644 --- a/src/DbConnectionPlus/DbConnectionExtensions.TemporaryTable.cs +++ b/src/DbConnectionPlus/DbConnectionExtensions.TemporaryTable.cs @@ -79,6 +79,8 @@ public static partial class DbConnectionExtensions /// The temporary table will contain a column for each instance property (with a public getter) of the passed /// objects. /// The name of each column will be the name of the corresponding property. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// The data type of each column will be compatible with the property type of the corresponding property. /// /// Example: diff --git a/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntities.cs b/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntities.cs index 7d0c175..4992be9 100644 --- a/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntities.cs +++ b/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntities.cs @@ -52,6 +52,8 @@ public static partial class DbConnectionExtensions /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. @@ -163,6 +165,8 @@ public static Int32 UpdateEntities( /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. diff --git a/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntity.cs b/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntity.cs index 27bdfe2..daebb17 100644 --- a/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntity.cs +++ b/src/DbConnectionPlus/DbConnectionExtensions.UpdateEntity.cs @@ -52,6 +52,8 @@ public static partial class DbConnectionExtensions /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. @@ -153,6 +155,8 @@ public static Int32 UpdateEntity( /// /// Each instance property of the type is mapped to a column with the same name /// (case-sensitive) in the table. + /// If a property is denoted with the , the name specified in the attribute is used + /// as the column name. /// /// /// The columns must have data types that are compatible with the property types of the corresponding properties. diff --git a/src/DbConnectionPlus/Entities/EntityHelper.cs b/src/DbConnectionPlus/Entities/EntityHelper.cs index b8253d1..afd9451 100644 --- a/src/DbConnectionPlus/Entities/EntityHelper.cs +++ b/src/DbConnectionPlus/Entities/EntityHelper.cs @@ -140,6 +140,7 @@ private static EntityTypeMetadata CreateEntityTypeMetadata(Type entityType) var property = properties[i]; propertiesMetadata[i] = new( + property.GetCustomAttribute()?.Name ?? property.Name, property.Name, property.PropertyType, property, diff --git a/src/DbConnectionPlus/Entities/EntityPropertyMetadata.cs b/src/DbConnectionPlus/Entities/EntityPropertyMetadata.cs index f6555e9..ad93215 100644 --- a/src/DbConnectionPlus/Entities/EntityPropertyMetadata.cs +++ b/src/DbConnectionPlus/Entities/EntityPropertyMetadata.cs @@ -9,6 +9,7 @@ namespace RentADeveloper.DbConnectionPlus.Entities; /// /// Metadata of an entity property. /// +/// The name of the column to which the property is mapped. /// The name of the property. /// The property type of the property. /// The property info of the property. @@ -26,6 +27,7 @@ namespace RentADeveloper.DbConnectionPlus.Entities; /// /// The database generated option for the property. public sealed record EntityPropertyMetadata( + String ColumnName, String PropertyName, Type PropertyType, PropertyInfo PropertyInfo, diff --git a/src/DbConnectionPlus/Materializers/EntityMaterializerFactory.cs b/src/DbConnectionPlus/Materializers/EntityMaterializerFactory.cs index 321fb6d..24fec21 100644 --- a/src/DbConnectionPlus/Materializers/EntityMaterializerFactory.cs +++ b/src/DbConnectionPlus/Materializers/EntityMaterializerFactory.cs @@ -172,9 +172,9 @@ Type[] dataReaderFieldTypes dataReaderFieldNames.Zip(dataReaderFieldTypes, (name, type) => (name, type)).ToArray() ); - var entityPropertiesByName = EntityHelper.GetEntityTypeMetadata(entityType) + var entityPropertiesByColumnName = EntityHelper.GetEntityTypeMetadata(entityType) .MappedProperties.Where(a => a.CanWrite) - .ToDictionary(a => a.PropertyName, StringComparer.OrdinalIgnoreCase); + .ToDictionary(a => a.ColumnName, StringComparer.OrdinalIgnoreCase); if (compatibleConstructor is not null) { @@ -202,7 +202,7 @@ Type[] dataReaderFieldTypes { var dataReaderFieldName = dataReaderFieldNames[fieldOrdinal]; - if (entityPropertiesByName.TryGetValue(dataReaderFieldName, out var entityProperty)) + if (entityPropertiesByColumnName.TryGetValue(dataReaderFieldName, out var entityProperty)) { fieldOrdinalToTargetType.Add( fieldOrdinal, @@ -225,7 +225,7 @@ Type[] dataReaderFieldTypes var dataReaderFieldName = dataReaderFieldNames[fieldOrdinal]; - if (compatibleConstructor is null && !entityPropertiesByName.ContainsKey(dataReaderFieldName)) + if (compatibleConstructor is null && !entityPropertiesByColumnName.ContainsKey(dataReaderFieldName)) { // No need to read the field when we are using properties to materialize and there is no matching // property for the field. @@ -369,7 +369,7 @@ Type[] dataReaderFieldTypes { var dataReaderFieldName = dataReaderFieldNames[fieldOrdinal]; - if (!entityPropertiesByName.TryGetValue(dataReaderFieldName, out var entityProperty)) + if (!entityPropertiesByColumnName.TryGetValue(dataReaderFieldName, out var entityProperty)) { continue; } @@ -495,15 +495,15 @@ Type[] dataReaderFieldTypes ); } - var entityPropertiesByName = EntityHelper.GetEntityTypeMetadata(entityType) + var entityPropertiesByColumnName = EntityHelper.GetEntityTypeMetadata(entityType) .MappedProperties.Where(a => a.CanWrite) - .ToDictionary(a => a.PropertyName, StringComparer.OrdinalIgnoreCase); + .ToDictionary(a => a.ColumnName, StringComparer.OrdinalIgnoreCase); for (var fieldOrdinal = 0; fieldOrdinal < dataReader.FieldCount; fieldOrdinal++) { var dataReaderFieldName = dataReaderFieldNames[fieldOrdinal]; - if (!entityPropertiesByName.TryGetValue(dataReaderFieldName, out var entityProperty)) + if (!entityPropertiesByColumnName.TryGetValue(dataReaderFieldName, out var entityProperty)) { continue; } diff --git a/src/DbConnectionPlus/SqlStatements/InterpolatedTemporaryTable.cs b/src/DbConnectionPlus/SqlStatements/InterpolatedTemporaryTable.cs index a2673ba..b35556c 100644 --- a/src/DbConnectionPlus/SqlStatements/InterpolatedTemporaryTable.cs +++ b/src/DbConnectionPlus/SqlStatements/InterpolatedTemporaryTable.cs @@ -7,8 +7,8 @@ namespace RentADeveloper.DbConnectionPlus.SqlStatements; /// A sequence of values, created from an expression in an interpolated string, to be passed to an SQL statement as a /// temporary table. /// -/// The name for the temporary table. -/// The values to populate the temporary table with. +/// The name for the table. +/// The values with which to populate the table. /// The type of values in . public readonly record struct InterpolatedTemporaryTable(String Name, IEnumerable Values, Type ValuesType) : IInterpolatedSqlStatementFragment; diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntitiesTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntitiesTests.cs index 0ea9360..5108423 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntitiesTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntitiesTests.cs @@ -138,6 +138,29 @@ public void DeleteEntities_MoreThan10Entities_ShouldBatchDeleteIfPossible() } } + [Fact] + public void DeleteEntities_MoreThan10Entities_ShouldUseConfiguredColumnNames() + { + // Some database adapters (like the SQL Server one) use batch deletion for more than 10 entities, so we need + // to test that as well. + + var entities = this.CreateEntitiesInDb(20); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + this.manipulator.DeleteEntities( + this.Connection, + entitiesWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + foreach (var entity in entities) + { + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + } + [Fact] public void DeleteEntities_MoreThan10EntitiesWithCompositeKey_ShouldBatchDeleteIfPossible() { @@ -201,6 +224,26 @@ public void DeleteEntities_ShouldReturnNumberOfAffectedRows() .Should().Be(0); } + [Fact] + public void DeleteEntities_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + this.manipulator.DeleteEntities( + this.Connection, + entitiesWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + foreach (var entity in entities) + { + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + } + [Fact] public void DeleteEntities_Transaction_ShouldUseTransaction() { @@ -339,6 +382,29 @@ await this.manipulator.DeleteEntitiesAsync( } } + [Fact] + public async Task DeleteEntitiesAsync_MoreThan10Entities_ShouldUseConfiguredColumnNames() + { + // Some database adapters (like the SQL Server one) use batch deletion for more than 10 entities, so we need + // to test that as well. + + var entities = this.CreateEntitiesInDb(20); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + await this.manipulator.DeleteEntitiesAsync( + this.Connection, + entitiesWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + foreach (var entity in entities) + { + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + } + [Fact] public async Task DeleteEntitiesAsync_ShouldHandleEntityWithCompositeKey() { @@ -380,6 +446,26 @@ public async Task DeleteEntitiesAsync_ShouldReturnNumberOfAffectedRows() .Should().Be(0); } + [Fact] + public async Task DeleteEntitiesAsync_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + await this.manipulator.DeleteEntitiesAsync( + this.Connection, + entitiesWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + foreach (var entity in entities) + { + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + } + [Fact] public async Task DeleteEntitiesAsync_Transaction_ShouldUseTransaction() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntityTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntityTests.cs index 5ec8c9b..64b57a9 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntityTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.DeleteEntityTests.cs @@ -146,6 +146,23 @@ public void DeleteEntity_ShouldReturnNumberOfAffectedRows() .Should().Be(0); } + [Fact] + public void DeleteEntity_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + this.manipulator.DeleteEntity( + this.Connection, + entityWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + [Fact] public void DeleteEntity_Transaction_ShouldUseTransaction() { @@ -286,6 +303,23 @@ public async Task DeleteEntityAsync_ShouldReturnNumberOfAffectedRows() .Should().Be(0); } + [Fact] + public async Task DeleteEntityAsync_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + await this.manipulator.DeleteEntityAsync( + this.Connection, + entityWithColumnAttributes, + null, + TestContext.Current.CancellationToken + ); + + this.ExistsEntityInDb(entity) + .Should().BeFalse(); + } + [Fact] public async Task DeleteEntityAsync_Transaction_ShouldUseTransaction() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntitiesTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntitiesTests.cs index 0a6c798..8b4f3d7 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntitiesTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntitiesTests.cs @@ -211,6 +211,25 @@ public void InsertEntities_ShouldSupportDateTimeOffsetValues() .Should().BeEquivalentTo(entities); } + [Fact] + public void InsertEntities_ShouldUseConfiguredColumnNames() + { + var entities = Generate.Multiple(); + + this.manipulator.InsertEntities( + this.Connection, + entities, + null, + TestContext.Current.CancellationToken + ); + + this.Connection.Query( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entities); + } + [Fact] public void InsertEntities_Transaction_ShouldUseTransaction() { @@ -456,6 +475,25 @@ await this.manipulator.InsertEntitiesAsync( .Should().BeEquivalentTo(entities); } + [Fact] + public async Task InsertEntitiesAsync_ShouldUseConfiguredColumnNames() + { + var entities = Generate.Multiple(); + + await this.manipulator.InsertEntitiesAsync( + this.Connection, + entities, + null, + TestContext.Current.CancellationToken + ); + + (await this.Connection.QueryAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ).ToListAsync(TestContext.Current.CancellationToken)) + .Should().BeEquivalentTo(entities); + } + [Fact] public async Task InsertEntitiesAsync_Transaction_ShouldUseTransaction() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntityTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntityTests.cs index 8921328..a613aa8 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntityTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.InsertEntityTests.cs @@ -165,7 +165,7 @@ public void InsertEntity_ShouldInsertEntity() $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -190,7 +190,21 @@ public void InsertEntity_ShouldSupportDateTimeOffsetValues() $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public void InsertEntity_ShouldUseConfiguredColumnNames() + { + var entity = Generate.Single(); + + this.manipulator.InsertEntity(this.Connection, entity, null, TestContext.Current.CancellationToken); + + this.Connection.QuerySingle( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entity); } [Fact] @@ -361,7 +375,7 @@ public async Task InsertEntityAsync_ShouldInsertEntity() $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -386,7 +400,21 @@ public async Task InsertEntityAsync_ShouldSupportDateTimeOffsetValues() $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public async Task InsertEntityAsync_ShouldUseConfiguredColumnNames() + { + var entity = Generate.Single(); + + await this.manipulator.InsertEntityAsync(this.Connection, entity, null, TestContext.Current.CancellationToken); + + (await this.Connection.QuerySingleAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(entity); } [Fact] diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntitiesTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntitiesTests.cs index dbb6c89..315fe08 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntitiesTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntitiesTests.cs @@ -296,6 +296,21 @@ public void UpdateEntities_ShouldUpdateEntities() .Should().BeEquivalentTo(updatedEntities); } + [Fact] + public void UpdateEntities_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var updatedEntities = Generate.UpdatesFor(entities); + + this.manipulator.UpdateEntities(this.Connection, updatedEntities, null, TestContext.Current.CancellationToken); + + this.Connection.Query( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(updatedEntities); + } + [Fact] public void UpdateEntities_Transaction_ShouldUseTransaction() { @@ -617,6 +632,26 @@ public async Task UpdateEntitiesAsync_ShouldUpdateEntities() .Should().BeEquivalentTo(updatedEntities); } + [Fact] + public async Task UpdateEntitiesAsync_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var updatedEntities = Generate.UpdatesFor(entities); + + await this.manipulator.UpdateEntitiesAsync( + this.Connection, + updatedEntities, + null, + TestContext.Current.CancellationToken + ); + + (await this.Connection.QueryAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ).ToListAsync()) + .Should().BeEquivalentTo(updatedEntities); + } + [Fact] public async Task UpdateEntitiesAsync_Transaction_ShouldUseTransaction() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntityTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntityTests.cs index a2e9768..002da97 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntityTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/EntityManipulator.UpdateEntityTests.cs @@ -51,7 +51,7 @@ public void UpdateEntity_CancellationToken_ShouldCancelOperationIfCancellationIs $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -82,7 +82,7 @@ public void UpdateEntity_EntityWithoutTableAttribute_ShouldUseEntityTypeNameAsTa $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -273,7 +273,7 @@ public void UpdateEntity_ShouldSupportDateTimeOffsetValues() $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -288,7 +288,22 @@ public void UpdateEntity_ShouldUpdateEntity() $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); + } + + [Fact] + public void UpdateEntity_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var updatedEntity = Generate.UpdateFor(entity); + + this.manipulator.UpdateEntity(this.Connection, updatedEntity, null, TestContext.Current.CancellationToken); + + this.Connection.QuerySingle( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -309,13 +324,13 @@ public void UpdateEntity_Transaction_ShouldUseTransaction() .Should().Be(1); this.Connection.QuerySingle($"SELECT * FROM {Q("Entity")}", transaction) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); transaction.Rollback(); } this.Connection.QuerySingle($"SELECT * FROM {Q("Entity")}") - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -341,7 +356,7 @@ await Invoking(() => $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -377,7 +392,7 @@ await this.manipulator.UpdateEntityAsync( $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -397,7 +412,7 @@ await this.manipulator.UpdateEntityAsync( $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -578,7 +593,7 @@ await this.manipulator.UpdateEntityAsync( $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -599,7 +614,27 @@ public async Task UpdateEntityAsync_ShouldUpdateEntity() $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); + } + + [Fact] + public async Task UpdateEntityAsync_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var updatedEntity = Generate.UpdateFor(entity); + + await this.manipulator.UpdateEntityAsync( + this.Connection, + updatedEntity, + null, + TestContext.Current.CancellationToken + ); + + (await this.Connection.QuerySingleAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(updatedEntity); } [Fact] @@ -620,13 +655,13 @@ public async Task UpdateEntityAsync_Transaction_ShouldUseTransaction() .Should().Be(1); (await this.Connection.QuerySingleAsync($"SELECT * FROM {Q("Entity")}", transaction)) - .Should().Be(updatedEntity); + .Should().BeEquivalentTo(updatedEntity); await transaction.RollbackAsync(); } (await this.Connection.QuerySingleAsync($"SELECT * FROM {Q("Entity")}")) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } private readonly IEntityManipulator manipulator; diff --git a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/TemporaryTableBuilderTests.cs b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/TemporaryTableBuilderTests.cs index b629791..8cfa523 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/TemporaryTableBuilderTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/TemporaryTableBuilderTests.cs @@ -87,7 +87,7 @@ public void BuildTemporaryTable_ComplexObjects_EnumSerializationModeIsIntegers_S foreach (var entity in entities) { reader.Read(); - + reader.GetInt32(0) .Should().Be((Int32)entity.Enum); } @@ -126,7 +126,7 @@ public void BuildTemporaryTable_ComplexObjects_EnumSerializationModeIsStrings_Sh foreach (var entity in entities) { reader.Read(); - + reader.GetString(0) .Should().Be(entity.Enum.ToString()); } @@ -241,6 +241,28 @@ public void BuildTemporaryTable_ComplexObjects_ShouldUseCollationOfDatabaseForTe .Should().Be(this.TestDatabaseProvider.DatabaseCollation); } + [Fact] + public void BuildTemporaryTable_ComplexObjects_ShouldUseConfiguredColumnNames() + { + var entities = Generate.Multiple(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + using var tableDisposer = this.builder.BuildTemporaryTable( + this.Connection, + null, + "Objects", + entitiesWithColumnAttributes, + typeof(EntityWithColumnAttributes), + TestContext.Current.CancellationToken + ); + + this.Connection.Query( + $"SELECT * FROM {QT("Objects")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entities); + } + [Fact] public void BuildTemporaryTable_ScalarValues_DateTimeOffsetValues_ShouldSupportDateTimeOffset() { @@ -297,7 +319,7 @@ public void BuildTemporaryTable_ScalarValues_EnumSerializationModeIsIntegers_Sho foreach (var value in values) { reader.Read(); - + reader.GetInt32(0) .Should().Be((Int32)value); } @@ -336,7 +358,7 @@ public void BuildTemporaryTable_ScalarValues_EnumSerializationModeIsStrings_Shou foreach (var value in values) { reader.Read(); - + reader.GetString(0) .Should().Be(value.ToString()); } @@ -529,7 +551,7 @@ public async Task foreach (var entity in entities) { await reader.ReadAsync(TestContext.Current.CancellationToken); - + reader.GetInt32(0) .Should().Be((Int32)entity.Enum); } @@ -569,7 +591,7 @@ public async Task foreach (var entity in entities) { await reader.ReadAsync(TestContext.Current.CancellationToken); - + reader.GetString(0) .Should().Be(entity.Enum.ToString()); } @@ -664,6 +686,28 @@ public async Task BuildTemporaryTableAsync_ComplexObjects_ShouldUseCollationOfDa .Should().Be(this.TestDatabaseProvider.DatabaseCollation); } + [Fact] + public async Task BuildTemporaryTableAsync_ComplexObjects_ShouldUseConfiguredColumnNames() + { + var entities = Generate.Multiple(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + await using var tableDisposer = await this.builder.BuildTemporaryTableAsync( + this.Connection, + null, + "Objects", + entitiesWithColumnAttributes, + typeof(EntityWithColumnAttributes), + TestContext.Current.CancellationToken + ); + + (await this.Connection.QueryAsync( + $"SELECT * FROM {QT("Objects")}", + cancellationToken: TestContext.Current.CancellationToken + ).ToListAsync()) + .Should().BeEquivalentTo(entities); + } + [Fact] public async Task BuildTemporaryTableAsync_ComplexObjects_WithNullables_ShouldHandleNullValues() { @@ -742,7 +786,7 @@ public async Task foreach (var value in values) { await reader.ReadAsync(TestContext.Current.CancellationToken); - + reader.GetInt32(0) .Should().Be((Int32)value); } @@ -782,7 +826,7 @@ public async Task foreach (var value in values) { await reader.ReadAsync(TestContext.Current.CancellationToken); - + reader.GetString(0) .Should().Be(value.ToString()); } diff --git a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOfTTests.cs b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOfTTests.cs index 7586674..77ab4d6 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOfTTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOfTTests.cs @@ -305,7 +305,7 @@ public void QueryFirst_EntityType_CharEntityProperty_ColumnContainsStringWithLen $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -568,6 +568,19 @@ public void QueryFirst_EntityType_ShouldSupportDateTimeOffsetValues() .Should().Be(entities[0]); } + [Fact] + public void QueryFirst_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + this.Connection.QueryFirst( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entityWithColumnAttributes); + } + [Fact] public void QueryFirst_EntityType_UnsupportedFieldType_ShouldThrow() { @@ -1207,7 +1220,7 @@ public async Task $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -1469,6 +1482,19 @@ public async Task QueryFirstAsync_EntityType_ShouldSupportDateTimeOffsetValues() .Should().Be(entities[0]); } + [Fact] + public async Task QueryFirstAsync_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + (await this.Connection.QueryFirstAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(entityWithColumnAttributes); + } + [Fact] public Task QueryFirstAsync_EntityType_UnsupportedFieldType_ShouldThrow() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOrDefaultOfTTests.cs b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOrDefaultOfTTests.cs index ea546b7..a5ba3d7 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOrDefaultOfTTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryFirstOrDefaultOfTTests.cs @@ -309,7 +309,7 @@ public void $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -572,6 +572,19 @@ public void QueryFirstOrDefault_EntityType_ShouldSupportDateTimeOffsetValues() .Should().Be(entities[0]); } + [Fact] + public void QueryFirstOrDefault_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + this.Connection.QueryFirstOrDefault( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entityWithColumnAttributes); + } + [Fact] public void QueryFirstOrDefault_EntityType_UnsupportedFieldType_ShouldThrow() { @@ -1223,7 +1236,7 @@ public async Task $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -1489,6 +1502,19 @@ public async Task QueryFirstOrDefaultAsync_EntityType_ShouldSupportDateTimeOffse .Should().Be(entities[0]); } + [Fact] + public async Task QueryFirstOrDefaultAsync_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + (await this.Connection.QueryFirstOrDefaultAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(entityWithColumnAttributes); + } + [Fact] public Task QueryFirstOrDefaultAsync_EntityType_UnsupportedFieldType_ShouldThrow() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryOfTTests.cs b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryOfTTests.cs index fd8e6fe..f62cc72 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryOfTTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QueryOfTTests.cs @@ -585,6 +585,19 @@ public void Query_EntityType_ShouldSupportDateTimeOffsetValues() .Should().BeEquivalentTo(entities); } + [Fact] + public void Query_EntityType_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + this.Connection.Query( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entitiesWithColumnAttributes); + } + [Fact] public void Query_EntityType_UnsupportedFieldType_ShouldThrow() { @@ -1505,6 +1518,19 @@ public async Task QueryAsync_EntityType_ShouldSupportDateTimeOffsetValues() .Should().BeEquivalentTo(entities); } + [Fact] + public async Task QueryAsync_EntityType_ShouldUseConfiguredColumnNames() + { + var entities = this.CreateEntitiesInDb(); + var entitiesWithColumnAttributes = Generate.MapTo(entities); + + (await this.Connection.QueryAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ).ToListAsync(TestContext.Current.CancellationToken)) + .Should().BeEquivalentTo(entitiesWithColumnAttributes); + } + [Fact] public Task QueryAsync_EntityType_UnsupportedFieldType_ShouldThrow() { diff --git a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOfTTests.cs b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOfTTests.cs index 873cf0c..92dc296 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOfTTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOfTTests.cs @@ -213,7 +213,7 @@ public void QuerySingle_CommandType_ShouldUseCommandType() commandType: CommandType.StoredProcedure, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -248,7 +248,7 @@ public void QuerySingle_ComplexObjectsTemporaryTable_ShouldPassInterpolatedObjec $"SELECT * FROM {TemporaryTable([entity])}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -305,7 +305,7 @@ public void QuerySingle_EntityType_CharEntityProperty_ColumnContainsStringWithLe $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -400,7 +400,7 @@ public void QuerySingle_EntityType_EntityTypeWithPropertiesWithDifferentCasing_S $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entityWithDifferentCasingProperties); + .Should().BeEquivalentTo(entityWithDifferentCasingProperties); } [Fact] @@ -504,7 +504,7 @@ public void $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -565,7 +565,20 @@ public void QuerySingle_EntityType_ShouldSupportDateTimeOffsetValues() $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public void QuerySingle_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + this.Connection.QuerySingle( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entityWithColumnAttributes); } [Fact] @@ -596,7 +609,7 @@ public void QuerySingle_InterpolatedParameter_ShouldPassInterpolatedParameter() $"SELECT * FROM {Q("Entity")} WHERE {Q("Id")} = {Parameter(entity.Id)}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -610,7 +623,7 @@ public void QuerySingle_Parameter_ShouldPassParameter() ); this.Connection.QuerySingle(statement, cancellationToken: TestContext.Current.CancellationToken) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -678,7 +691,7 @@ public void QuerySingle_ScalarValuesTemporaryTable_ShouldPassInterpolatedValuesA """, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -693,7 +706,7 @@ public void QuerySingle_Transaction_ShouldUseTransaction() transaction, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); transaction.Rollback(); } @@ -915,7 +928,7 @@ public void QuerySingle_ValueTupleType_ShouldSupportDateTimeOffsetValues() $"SELECT {Q("Id")}, {Q("DateTimeOffsetValue")} FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be((entity.Id, entity.DateTimeOffsetValue)); + .Should().BeEquivalentTo((entity.Id, entity.DateTimeOffsetValue)); } [Fact] @@ -1127,7 +1140,7 @@ public async Task QuerySingleAsync_CommandType_ShouldUseCommandType() commandType: CommandType.StoredProcedure, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1164,7 +1177,7 @@ public async Task $"SELECT * FROM {TemporaryTable([entity])}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1223,7 +1236,7 @@ public async Task $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -1319,7 +1332,7 @@ public async Task $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entityWithDifferentCasingProperties); + .Should().BeEquivalentTo(entityWithDifferentCasingProperties); } [Fact] @@ -1423,7 +1436,7 @@ public async Task $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1483,7 +1496,20 @@ public async Task QuerySingleAsync_EntityType_ShouldSupportDateTimeOffsetValues( $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public async Task QuerySingleAsync_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + (await this.Connection.QuerySingleAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(entityWithColumnAttributes); } [Fact] @@ -1514,7 +1540,7 @@ public async Task QuerySingleAsync_InterpolatedParameter_ShouldPassInterpolatedP $"SELECT * FROM {Q("Entity")} WHERE {Q("Id")} = {Parameter(entity.Id)}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1531,7 +1557,7 @@ public async Task QuerySingleAsync_Parameter_ShouldPassParameter() statement, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1602,7 +1628,7 @@ public async Task """, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1617,7 +1643,7 @@ public async Task QuerySingleAsync_Transaction_ShouldUseTransaction() transaction, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); await transaction.RollbackAsync(); } @@ -1840,7 +1866,7 @@ public async Task QuerySingleAsync_ValueTupleType_ShouldSupportDateTimeOffsetVal $"SELECT {Q("Id")}, {Q("DateTimeOffsetValue")} FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be((entity.Id, entity.DateTimeOffsetValue)); + .Should().BeEquivalentTo((entity.Id, entity.DateTimeOffsetValue)); } [Fact] diff --git a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOrDefaultOfTTests.cs b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOrDefaultOfTTests.cs index abe38e8..b601707 100644 --- a/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOrDefaultOfTTests.cs +++ b/tests/DbConnectionPlus.IntegrationTests/DbConnectionExtensions.QuerySingleOrDefaultOfTTests.cs @@ -215,7 +215,7 @@ public void QuerySingleOrDefault_CommandType_ShouldUseCommandType() commandType: CommandType.StoredProcedure, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -251,7 +251,7 @@ public void $"SELECT * FROM {TemporaryTable([entity])}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -309,7 +309,7 @@ public void $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -404,7 +404,7 @@ public void QuerySingleOrDefault_EntityType_EntityTypeWithPropertiesWithDifferen $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entityWithDifferentCasingProperties); + .Should().BeEquivalentTo(entityWithDifferentCasingProperties); } [Fact] @@ -508,7 +508,7 @@ public void $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -569,7 +569,20 @@ public void QuerySingleOrDefault_EntityType_ShouldSupportDateTimeOffsetValues() $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public void QuerySingleOrDefault_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + this.Connection.QuerySingleOrDefault( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + ) + .Should().BeEquivalentTo(entityWithColumnAttributes); } [Fact] @@ -600,7 +613,7 @@ public void QuerySingleOrDefault_InterpolatedParameter_ShouldPassInterpolatedPar $"SELECT * FROM {Q("Entity")} WHERE {Q("Id")} = {Parameter(entity.Id)}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -617,7 +630,7 @@ public void QuerySingleOrDefault_Parameter_ShouldPassParameter() statement, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -696,7 +709,7 @@ public void """, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -711,7 +724,7 @@ public void QuerySingleOrDefault_Transaction_ShouldUseTransaction() transaction, cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); transaction.Rollback(); } @@ -933,7 +946,7 @@ public void QuerySingleOrDefault_ValueTupleType_ShouldSupportDateTimeOffsetValue $"SELECT {Q("Id")}, {Q("DateTimeOffsetValue")} FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken ) - .Should().Be((entity.Id, entity.DateTimeOffsetValue)); + .Should().BeEquivalentTo((entity.Id, entity.DateTimeOffsetValue)); } [Fact] @@ -1146,7 +1159,7 @@ public async Task QuerySingleOrDefaultAsync_CommandType_ShouldUseCommandType() commandType: CommandType.StoredProcedure, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1183,7 +1196,7 @@ public async Task $"SELECT * FROM {TemporaryTable([entity])}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1242,7 +1255,7 @@ public async Task $"SELECT '{character}' AS {Q("Char")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(new EntityWithCharProperty { Char = character }); + .Should().BeEquivalentTo(new EntityWithCharProperty { Char = character }); } [Fact] @@ -1339,7 +1352,7 @@ public async Task $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entityWithDifferentCasingProperties); + .Should().BeEquivalentTo(entityWithDifferentCasingProperties); } [Fact] @@ -1447,7 +1460,7 @@ public async Task $"SELECT * FROM {Q("Entity")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1507,7 +1520,20 @@ public async Task QuerySingleOrDefaultAsync_EntityType_ShouldSupportDateTimeOffs $"SELECT * FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); + } + + [Fact] + public async Task QuerySingleOrDefaultAsync_EntityType_ShouldUseConfiguredColumnNames() + { + var entity = this.CreateEntityInDb(); + var entityWithColumnAttributes = Generate.MapTo(entity); + + (await this.Connection.QuerySingleOrDefaultAsync( + $"SELECT * FROM {Q("Entity")}", + cancellationToken: TestContext.Current.CancellationToken + )) + .Should().BeEquivalentTo(entityWithColumnAttributes); } [Fact] @@ -1538,7 +1564,7 @@ public async Task QuerySingleOrDefaultAsync_InterpolatedParameter_ShouldPassInte $"SELECT * FROM {Q("Entity")} WHERE {Q("Id")} = {Parameter(entity.Id)}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1555,7 +1581,7 @@ public async Task QuerySingleOrDefaultAsync_Parameter_ShouldPassParameter() statement, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1636,7 +1662,7 @@ public async Task """, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); } [Fact] @@ -1651,7 +1677,7 @@ public async Task QuerySingleOrDefaultAsync_Transaction_ShouldUseTransaction() transaction, cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be(entity); + .Should().BeEquivalentTo(entity); await transaction.RollbackAsync(); } @@ -1878,7 +1904,7 @@ public async Task QuerySingleOrDefaultAsync_ValueTupleType_ShouldSupportDateTime $"SELECT {Q("Id")}, {Q("DateTimeOffsetValue")} FROM {Q("EntityWithDateTimeOffset")}", cancellationToken: TestContext.Current.CancellationToken )) - .Should().Be((entity.Id, entity.DateTimeOffsetValue)); + .Should().BeEquivalentTo((entity.Id, entity.DateTimeOffsetValue)); } [Fact] diff --git a/tests/DbConnectionPlus.IntegrationTests/IntegrationTestsBase.cs b/tests/DbConnectionPlus.IntegrationTests/IntegrationTestsBase.cs index 6281c97..bc4a49a 100644 --- a/tests/DbConnectionPlus.IntegrationTests/IntegrationTestsBase.cs +++ b/tests/DbConnectionPlus.IntegrationTests/IntegrationTestsBase.cs @@ -205,7 +205,7 @@ SELECT 1 WHERE { String.Join( " AND ", - [.. keyProperties.Select(p => $"{Q(p.PropertyName)} = {P(p.PropertyName)}")] + [.. keyProperties.Select(p => $"{Q(p.ColumnName)} = {P(p.PropertyName)}")] ) } """, diff --git a/tests/DbConnectionPlus.UnitTests/DbCommands/DbCommandBuilderTests.cs b/tests/DbConnectionPlus.UnitTests/DbCommands/DbCommandBuilderTests.cs index c82b219..0aa1d76 100644 --- a/tests/DbConnectionPlus.UnitTests/DbCommands/DbCommandBuilderTests.cs +++ b/tests/DbConnectionPlus.UnitTests/DbCommands/DbCommandBuilderTests.cs @@ -483,7 +483,7 @@ WHERE Entities.Id IN (SELECT Value FROM {TemporaryTable(entityIds)}) .Should().StartWith("EntityIds_"); table2.Values - .Should().Be(entityIds); + .Should().BeEquivalentTo(entityIds); table2.ValuesType .Should().Be(typeof(Int64)); @@ -1081,7 +1081,7 @@ WHERE Entities.Id IN (SELECT Value FROM {TemporaryTable(entityIds)}) .Should().StartWith("EntityIds_"); table2.Values - .Should().Be(entityIds); + .Should().BeEquivalentTo(entityIds); table2.ValuesType .Should().Be(typeof(Int64)); diff --git a/tests/DbConnectionPlus.UnitTests/Entities/EntityHelperTests.cs b/tests/DbConnectionPlus.UnitTests/Entities/EntityHelperTests.cs index 9c387cc..d7a1fe9 100644 --- a/tests/DbConnectionPlus.UnitTests/Entities/EntityHelperTests.cs +++ b/tests/DbConnectionPlus.UnitTests/Entities/EntityHelperTests.cs @@ -157,6 +157,7 @@ public void FindParameterlessConstructor_PublicParameterlessConstructor_ShouldRe [InlineData(typeof(Entity))] [InlineData(typeof(EntityWithTableAttribute))] [InlineData(typeof(EntityWithIdentityAndComputedProperties))] + [InlineData(typeof(EntityWithColumnAttributes))] public void GetEntityTypeMetadata_ShouldGetMetadataForEntityType(Type entityType) { var faker = new Faker(); @@ -234,6 +235,9 @@ public void GetEntityTypeMetadata_ShouldGetMetadataForEntityType(Type entityType propertyMetadata .Should().NotBeNull(); + propertyMetadata.ColumnName + .Should().Be(property.GetCustomAttribute()?.Name ?? property.Name); + propertyMetadata.PropertyName .Should().Be(property.Name); diff --git a/tests/DbConnectionPlus.UnitTests/Materializers/EntityMaterializerFactoryTests.cs b/tests/DbConnectionPlus.UnitTests/Materializers/EntityMaterializerFactoryTests.cs index 5e759d2..25b4047 100644 --- a/tests/DbConnectionPlus.UnitTests/Materializers/EntityMaterializerFactoryTests.cs +++ b/tests/DbConnectionPlus.UnitTests/Materializers/EntityMaterializerFactoryTests.cs @@ -598,6 +598,24 @@ public void Materializer_PropertiesWithDifferentCasing_ShouldMatchPropertiesCase .Should().BeEquivalentTo(entityWithDifferentCasingProperties); } + [Fact] + public void Materializer_ShouldUseConfiguredColumnNames() + { + var entities = Generate.Multiple(1); + var entityWithColumnAttribute = Generate.MapTo(entities[0]); + + var dataReader = new EnumHandlingObjectReader(typeof(Entity), entities); + + dataReader.Read(); + + var materializer = EntityMaterializerFactory.GetMaterializer(dataReader); + + var materializedEntity = materializer(dataReader); + + materializedEntity + .Should().BeEquivalentTo(entityWithColumnAttribute); + } + [Fact] public void Materializer_ShouldMaterializeBinaryData() { diff --git a/tests/DbConnectionPlus.UnitTests/PublicApiTest.PublicApiHasNotChanged.verified.txt b/tests/DbConnectionPlus.UnitTests/PublicApiTest.PublicApiHasNotChanged.verified.txt index e1bffd5..2e82db3 100644 --- a/tests/DbConnectionPlus.UnitTests/PublicApiTest.PublicApiHasNotChanged.verified.txt +++ b/tests/DbConnectionPlus.UnitTests/PublicApiTest.PublicApiHasNotChanged.verified.txt @@ -4,6 +4,7 @@ namespace RentADeveloper.DbConnectionPlus.DatabaseAdapters public static class Constants { public const string Indent = " "; + public const string SingleColumnTemporaryTableColumnName = "Value"; } public static class DatabaseAdapterRegistry { @@ -180,9 +181,10 @@ namespace RentADeveloper.DbConnectionPlus.Entities } public sealed record EntityPropertyMetadata : System.IEquatable { - public EntityPropertyMetadata(string PropertyName, System.Type PropertyType, System.Reflection.PropertyInfo PropertyInfo, bool IsNotMapped, bool IsKeyProperty, bool CanRead, bool CanWrite, Fasterflect.MemberGetter? PropertyGetter, Fasterflect.MemberSetter? PropertySetter, System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption DatabaseGeneratedOption) { } + public EntityPropertyMetadata(string ColumnName, string PropertyName, System.Type PropertyType, System.Reflection.PropertyInfo PropertyInfo, bool IsNotMapped, bool IsKeyProperty, bool CanRead, bool CanWrite, Fasterflect.MemberGetter? PropertyGetter, Fasterflect.MemberSetter? PropertySetter, System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption DatabaseGeneratedOption) { } public bool CanRead { get; init; } public bool CanWrite { get; init; } + public string ColumnName { get; init; } public System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption DatabaseGeneratedOption { get; init; } public bool IsKeyProperty { get; init; } public bool IsNotMapped { get; init; } @@ -194,12 +196,11 @@ namespace RentADeveloper.DbConnectionPlus.Entities } public sealed record EntityTypeMetadata : System.IEquatable { - public EntityTypeMetadata(System.Type EntityType, string TableName, System.Collections.Generic.IReadOnlyList AllProperties, System.Collections.Generic.IReadOnlyDictionary AllPropertiesByPropertyName, System.Collections.Generic.IReadOnlyList MappedProperties, System.Collections.Generic.IReadOnlyList KeyProperties, System.Collections.Generic.IReadOnlyList InsertProperties, System.Collections.Generic.IReadOnlyList UpdateProperties, System.Collections.Generic.IReadOnlyList ComputedProperties, System.Collections.Generic.IReadOnlyList IdentityAndComputedProperties) { } + public EntityTypeMetadata(System.Type EntityType, string TableName, System.Collections.Generic.IReadOnlyList AllProperties, System.Collections.Generic.IReadOnlyDictionary AllPropertiesByPropertyName, System.Collections.Generic.IReadOnlyList MappedProperties, System.Collections.Generic.IReadOnlyList KeyProperties, System.Collections.Generic.IReadOnlyList InsertProperties, System.Collections.Generic.IReadOnlyList UpdateProperties, System.Collections.Generic.IReadOnlyList DatabaseGeneratedProperties) { } public System.Collections.Generic.IReadOnlyList AllProperties { get; init; } public System.Collections.Generic.IReadOnlyDictionary AllPropertiesByPropertyName { get; init; } - public System.Collections.Generic.IReadOnlyList ComputedProperties { get; init; } + public System.Collections.Generic.IReadOnlyList DatabaseGeneratedProperties { get; init; } public System.Type EntityType { get; init; } - public System.Collections.Generic.IReadOnlyList IdentityAndComputedProperties { get; init; } public System.Collections.Generic.IReadOnlyList InsertProperties { get; init; } public System.Collections.Generic.IReadOnlyList KeyProperties { get; init; } public System.Collections.Generic.IReadOnlyList MappedProperties { get; init; } diff --git a/tests/DbConnectionPlus.UnitTests/TestData/Entity.cs b/tests/DbConnectionPlus.UnitTests/TestData/Entity.cs index 3f06d4c..5ed98c7 100644 --- a/tests/DbConnectionPlus.UnitTests/TestData/Entity.cs +++ b/tests/DbConnectionPlus.UnitTests/TestData/Entity.cs @@ -22,4 +22,4 @@ public record Entity public String StringValue { get; set; } = null!; public TimeOnly TimeOnlyValue { get; set; } public TimeSpan TimeSpanValue { get; set; } -} +} \ No newline at end of file diff --git a/tests/DbConnectionPlus.UnitTests/TestData/EntityWithColumnAttributes.cs b/tests/DbConnectionPlus.UnitTests/TestData/EntityWithColumnAttributes.cs new file mode 100644 index 0000000..1488913 --- /dev/null +++ b/tests/DbConnectionPlus.UnitTests/TestData/EntityWithColumnAttributes.cs @@ -0,0 +1,57 @@ +namespace RentADeveloper.DbConnectionPlus.UnitTests.TestData; + +[Table("Entity")] +public record EntityWithColumnAttributes +{ + [Column("BooleanValue")] + public Boolean ValueBoolean { get; set; } + + [Column("ByteValue")] + public Byte ValueByte { get; set; } + + [Column("CharValue")] + public Char ValueChar { get; set; } + + [Column("DateOnlyValue")] + public DateOnly ValueDateOnly { get; set; } + + [Column("DateTimeValue")] + public DateTime ValueDateTime { get; set; } + + [Column("DecimalValue")] + public Decimal ValueDecimal { get; set; } + + [Column("DoubleValue")] + public Double ValueDouble { get; set; } + + [Column("EnumValue")] + public TestEnum ValueEnum { get; set; } + + [Column("GuidValue")] + public Guid ValueGuid { get; set; } + + [Key] + [Column("Id")] + public Int64 ValueId { get; set; } + + [Column("Int16Value")] + public Int16 ValueInt16 { get; set; } + + [Column("Int32Value")] + public Int32 ValueInt32 { get; set; } + + [Column("Int64Value")] + public Int64 ValueInt64 { get; set; } + + [Column("SingleValue")] + public Single ValueSingle { get; set; } + + [Column("StringValue")] + public String ValueString { get; set; } = null!; + + [Column("TimeOnlyValue")] + public TimeOnly ValueTimeOnly { get; set; } + + [Column("TimeSpanValue")] + public TimeSpan ValueTimeSpan { get; set; } +} diff --git a/tests/DbConnectionPlus.UnitTests/TestData/Generate.cs b/tests/DbConnectionPlus.UnitTests/TestData/Generate.cs index 207158e..00d6d1e 100644 --- a/tests/DbConnectionPlus.UnitTests/TestData/Generate.cs +++ b/tests/DbConnectionPlus.UnitTests/TestData/Generate.cs @@ -106,6 +106,30 @@ static Generate() TypeAdapterConfig .NewConfig() .NameMatchingStrategy(NameMatchingStrategy.IgnoreCase); + + TypeAdapterConfig + .NewConfig() + .ConstructUsing(entity => new() + { + ValueId = entity.Id, + ValueBoolean = entity.BooleanValue, + ValueByte = entity.ByteValue, + ValueChar = entity.CharValue, + ValueDateOnly = entity.DateOnlyValue, + ValueDateTime = entity.DateTimeValue, + ValueDecimal = entity.DecimalValue, + ValueDouble = entity.DoubleValue, + ValueEnum = entity.EnumValue, + ValueGuid = entity.GuidValue, + ValueInt16 = entity.Int16Value, + ValueInt32 = entity.Int32Value, + ValueInt64 = entity.Int64Value, + ValueSingle = entity.SingleValue, + ValueString = entity.StringValue, + ValueTimeSpan = entity.TimeSpanValue, + ValueTimeOnly = entity.TimeOnlyValue + } + ); } ///