diff --git a/README.md b/README.md index 7440c6a..1b2c60f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,117 @@ # EnumerableDataReaderAdapter -An adapter that allows converting an IEnumerable to IDataReader that can be used as a data source for SqlBulkCopy. +A lightweight .NET library that converts `IEnumerable` into an `IDataReader`, enabling streaming of in-memory collections into APIs that require `IDataReader` -- most notably `SqlBulkCopy` for high-performance bulk inserts into SQL Server. + +## Features + +- **Streaming** -- rows are read lazily from the enumerable; the entire collection is never buffered in memory. +- **Automatic property mapping** -- public properties are discovered automatically when no explicit mapping is provided. +- **Fluent column mapping API** -- choose exactly which columns to expose using expression-based or delegate-based mappings. +- **Computed columns** -- map constant values or derived expressions that don't correspond to a property. +- **Multi-target** -- supports .NET 8.0, .NET 9.0, and .NET 10.0. + +## Installation + +Add a project reference or include the source in your solution. The library has no external runtime dependencies. ## Usage -```c# -var data = Enumerable.Range(1, 10000).Select(x => new { Id = x, Name = $"name-{x}" }); +### Basic usage with SqlBulkCopy + +```csharp +var data = Enumerable.Range(1, 10_000) + .Select(x => new { Id = x, Name = $"name-{x}" }); + +using var reader = data.ToDataReader(map => map + .Add(x => x.Id) + .Add(x => x.Name)); + +using var bulkCopy = new SqlBulkCopy(connectionString); +bulkCopy.DestinationTableName = "dbo.People"; +bulkCopy.EnableStreaming = true; +bulkCopy.ColumnMappings.Add("Id", "Id"); +bulkCopy.ColumnMappings.Add("Name", "Name"); + +await bulkCopy.WriteToServerAsync(reader); +``` + +### Default mapping (auto-discover all public properties) + +If no mapping is configured, every public instance property on `T` is exposed as a column: + +```csharp +var reader = products.ToDataReader(); +``` + +### Expression-based mapping + +Use lambda expressions that point to properties. The column name and type are inferred automatically: + +```csharp +var reader = products.ToDataReader(map => map + .Add(p => p.Id) + .Add(p => p.Name) + .Add(p => p.Price)); +``` + +### Delegate-based mapping with explicit name and type + +For full control -- including computed/constant columns -- specify the column name, CLR type, and a value delegate: + +```csharp +var reader = orders.ToDataReader(map => map + .Add("OrderId", typeof(int), o => o.Id) + .Add("Total", typeof(decimal), o => o.Quantity * o.UnitPrice) + .Add("Source", typeof(string), _ => "Import")); +``` + +### Mixing mapping styles + +The two `Add` overloads can be freely combined in a single mapping configuration: + +```csharp +var reader = items.ToDataReader(map => map + .Add(i => i.Id) + .Add("DisplayName", typeof(string), i => $"{i.FirstName} {i.LastName}") + .Add(i => i.CreatedAt)); +``` + +## API Reference + +### `EnumerableExtensions` + +| Method | Description | +|--------|-------------| +| `ToDataReader(this IEnumerable, Action>?)` | Creates an `IDataReader` with optional mapping configuration. When no columns are configured, all public properties of `T` are used. | +| `ToDataReader(this IEnumerable, ColumnMappings)` | Creates an `IDataReader` using a pre-built `ColumnMappings` instance. | + +### `ColumnMappings` + +| Method | Description | +|--------|-------------| +| `Add(Expression>)` | Adds a column from a property expression. Column name and type are inferred from the member. | +| `Add(string, Type, Func)` | Adds a column with an explicit name, CLR type, and value delegate. | + +Both `Add` methods return `this`, so calls can be chained fluently. + +## Building + +```bash +dotnet build +``` + +## Running tests + +```bash +dotnet test +``` + +## Benchmarks + +```bash +dotnet run --project benchmarks/EnumerableDataReaderAdapter.Benchmarks -c Release +``` -using var reader = data.ToDataReader(map => map.Add(x => x.Id).Add(x => x.Name)); +## License -using var sqlBulkCopy = new SqlBulkCopy(connectionString); -sqlBulkCopy.BatchSize = 10000; -sqlBulkCopy.ColumnMappings.Add("Id", "Id"); -sqlBulkCopy.ColumnMappings.Add("Name", "Name"); -sqlBulkCopy.DestinationTableName = "dbo.TableName"; -sqlBulkCopy.EnableStreaming = true; -await sqlBulkCopy.WriteToServerAsync(reader); -``` \ No newline at end of file +[MIT](LICENSE) -- Copyright (c) 2018 Dimo Terziev