Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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 ####

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
237 changes: 120 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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\<T\> / QueryAsync\<T\>](#queryt--queryasynct)
- [QueryFirst\<T\> / QueryFirstAsync\<T\>](#queryfirstt--queryfirstasynct)
- [QueryFirstOrDefault\<T\> / QueryFirstOrDefaultAsync\<T\>](#queryfirstordefaultt--queryfirstordefaultasynct)
- [QuerySingle\<T\> / QuerySingleAsync\<T\>](#querysinglet--querysingleasynct)
- [QuerySingleOrDefault\<T\> / QuerySingleOrDefaultAsync\<T\>](#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\<T\> / QueryAsync\<T\>](#queryt--queryasynct)
- [QueryFirst\<T\> / QueryFirstAsync\<T\>](#queryfirstt--queryfirstasynct)
- [QueryFirstOrDefault\<T\> / QueryFirstOrDefaultAsync\<T\>](#queryfirstordefaultt--queryfirstordefaultasynct)
- [QuerySingle\<T\> / QuerySingleAsync\<T\>](#querysinglet--querysingleasynct)
- [QuerySingleOrDefault\<T\> / QuerySingleOrDefaultAsync\<T\>](#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
Expand All @@ -91,7 +85,6 @@ manager console:
PM> Install-Package RentADeveloper.DbConnectionPlus
```

## Quick start
Import the library and the static helpers:

```csharp
Expand Down Expand Up @@ -329,20 +322,23 @@ 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)
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/DbConnectionPlus/DatabaseAdapters/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ public static class Constants
/// The string to use to indent parts of SQL statements.
/// </summary>
public const String Indent = " ";

/// <summary>
/// The name to use for the single column of single column temporary tables.
/// </summary>
public const String SingleColumnTemporaryTableColumnName = "Value";
}
28 changes: 14 additions & 14 deletions src/DbConnectionPlus/DatabaseAdapters/ITemporaryTableBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ public interface ITemporaryTableBuilder
/// <summary>
/// Builds a temporary table and populates it with the specified values.
/// </summary>
/// <param name="connection">The database connection to use to build the temporary table.</param>
/// <param name="transaction">The database transaction within to build the temporary table.</param>
/// <param name="name">The name of the temporary table to build.</param>
/// <param name="values">The values to populate the temporary table with.</param>
/// <param name="connection">The database connection to use to build the table.</param>
/// <param name="transaction">The database transaction within to build the table.</param>
/// <param name="name">The name of the table to build.</param>
/// <param name="values">The values with which to populate the table.</param>
/// <param name="valuesType">The type of values in <paramref name="values" />.</param>
/// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
/// <returns>
Expand Down Expand Up @@ -65,13 +65,13 @@ public interface ITemporaryTableBuilder
/// <para>
/// If the type <paramref name="valuesType" /> is a scalar type
/// (e.g. <see cref="String" />, <see cref="Int32" />, <see cref="DateTime" />, <see cref="Enum" /> 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 <paramref name="valuesType" />.
/// </para>
/// <para>
/// If the type <paramref name="valuesType" /> 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
/// <paramref name="valuesType" />.
/// 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.
Expand All @@ -89,10 +89,10 @@ public TemporaryTableDisposer BuildTemporaryTable(
/// <summary>
/// Asynchronously builds a temporary table and populates it with the specified values.
/// </summary>
/// <param name="connection">The database connection to use to build the temporary table.</param>
/// <param name="transaction">The database transaction within to build the temporary table.</param>
/// <param name="name">The name of the temporary table to build.</param>
/// <param name="values">The values to populate the temporary table with.</param>
/// <param name="connection">The database connection to use to build the table.</param>
/// <param name="transaction">The database transaction within to build the table.</param>
/// <param name="name">The name of the table to build.</param>
/// <param name="values">The values with which to populate the table.</param>
/// <param name="valuesType">The type of values in <paramref name="values" />.</param>
/// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
/// <returns>
Expand Down Expand Up @@ -145,13 +145,13 @@ public TemporaryTableDisposer BuildTemporaryTable(
/// <para>
/// If the type <paramref name="valuesType" /> is a scalar type
/// (e.g. <see cref="String" />, <see cref="Int32" />, <see cref="DateTime" />, <see cref="Enum" /> 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 <paramref name="valuesType" />.
/// </para>
/// <para>
/// If the type <paramref name="valuesType" /> 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
/// <paramref name="valuesType" />.
/// 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.
Expand Down
Loading