Skip to content

Commit da141d8

Browse files
claudedterziev
authored andcommitted
Rewrite README with comprehensive project documentation
Cover features, installation, usage examples (default mapping, expression-based, delegate-based, computed columns, SqlBulkCopy integration), API reference, build/test/benchmark instructions, and license info. https://claude.ai/code/session_01DmmRHcgn5V1ZSmQbKT3Jbh
1 parent 4a37076 commit da141d8

1 file changed

Lines changed: 110 additions & 12 deletions

File tree

README.md

Lines changed: 110 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,117 @@
11
# EnumerableDataReaderAdapter
22

3-
An adapter that allows converting an IEnumerable<T> to IDataReader that can be used as a data source for SqlBulkCopy.
3+
A lightweight .NET library that converts `IEnumerable<T>` 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.
4+
5+
## Features
6+
7+
- **Streaming** -- rows are read lazily from the enumerable; the entire collection is never buffered in memory.
8+
- **Automatic property mapping** -- public properties are discovered automatically when no explicit mapping is provided.
9+
- **Fluent column mapping API** -- choose exactly which columns to expose using expression-based or delegate-based mappings.
10+
- **Computed columns** -- map constant values or derived expressions that don't correspond to a property.
11+
- **Multi-target** -- supports .NET 8.0, .NET 9.0, and .NET 10.0.
12+
13+
## Installation
14+
15+
Add a project reference or include the source in your solution. The library has no external runtime dependencies.
416

517
## Usage
618

7-
```c#
8-
var data = Enumerable.Range(1, 10000).Select(x => new { Id = x, Name = $"name-{x}" });
19+
### Basic usage with SqlBulkCopy
20+
21+
```csharp
22+
var data = Enumerable.Range(1, 10_000)
23+
.Select(x => new { Id = x, Name = $"name-{x}" });
24+
25+
using var reader = data.ToDataReader(map => map
26+
.Add(x => x.Id)
27+
.Add(x => x.Name));
28+
29+
using var bulkCopy = new SqlBulkCopy(connectionString);
30+
bulkCopy.DestinationTableName = "dbo.People";
31+
bulkCopy.EnableStreaming = true;
32+
bulkCopy.ColumnMappings.Add("Id", "Id");
33+
bulkCopy.ColumnMappings.Add("Name", "Name");
34+
35+
await bulkCopy.WriteToServerAsync(reader);
36+
```
37+
38+
### Default mapping (auto-discover all public properties)
39+
40+
If no mapping is configured, every public instance property on `T` is exposed as a column:
41+
42+
```csharp
43+
var reader = products.ToDataReader();
44+
```
45+
46+
### Expression-based mapping
47+
48+
Use lambda expressions that point to properties. The column name and type are inferred automatically:
49+
50+
```csharp
51+
var reader = products.ToDataReader(map => map
52+
.Add(p => p.Id)
53+
.Add(p => p.Name)
54+
.Add(p => p.Price));
55+
```
56+
57+
### Delegate-based mapping with explicit name and type
58+
59+
For full control -- including computed/constant columns -- specify the column name, CLR type, and a value delegate:
60+
61+
```csharp
62+
var reader = orders.ToDataReader(map => map
63+
.Add("OrderId", typeof(int), o => o.Id)
64+
.Add("Total", typeof(decimal), o => o.Quantity * o.UnitPrice)
65+
.Add("Source", typeof(string), _ => "Import"));
66+
```
67+
68+
### Mixing mapping styles
69+
70+
The two `Add` overloads can be freely combined in a single mapping configuration:
71+
72+
```csharp
73+
var reader = items.ToDataReader(map => map
74+
.Add(i => i.Id)
75+
.Add("DisplayName", typeof(string), i => $"{i.FirstName} {i.LastName}")
76+
.Add(i => i.CreatedAt));
77+
```
78+
79+
## API Reference
80+
81+
### `EnumerableExtensions`
82+
83+
| Method | Description |
84+
|--------|-------------|
85+
| `ToDataReader<T>(this IEnumerable<T>, Action<ColumnMappings<T>>?)` | Creates an `IDataReader` with optional mapping configuration. When no columns are configured, all public properties of `T` are used. |
86+
| `ToDataReader<T>(this IEnumerable<T>, ColumnMappings<T>)` | Creates an `IDataReader` using a pre-built `ColumnMappings<T>` instance. |
87+
88+
### `ColumnMappings<T>`
89+
90+
| Method | Description |
91+
|--------|-------------|
92+
| `Add(Expression<Func<T, object?>>)` | Adds a column from a property expression. Column name and type are inferred from the member. |
93+
| `Add(string, Type, Func<T, object?>)` | Adds a column with an explicit name, CLR type, and value delegate. |
94+
95+
Both `Add` methods return `this`, so calls can be chained fluently.
96+
97+
## Building
98+
99+
```bash
100+
dotnet build
101+
```
102+
103+
## Running tests
104+
105+
```bash
106+
dotnet test
107+
```
108+
109+
## Benchmarks
110+
111+
```bash
112+
dotnet run --project benchmarks/EnumerableDataReaderAdapter.Benchmarks -c Release
113+
```
9114

10-
using var reader = data.ToDataReader(map => map.Add(x => x.Id).Add(x => x.Name));
115+
## License
11116

12-
using var sqlBulkCopy = new SqlBulkCopy(connectionString);
13-
sqlBulkCopy.BatchSize = 10000;
14-
sqlBulkCopy.ColumnMappings.Add("Id", "Id");
15-
sqlBulkCopy.ColumnMappings.Add("Name", "Name");
16-
sqlBulkCopy.DestinationTableName = "dbo.TableName";
17-
sqlBulkCopy.EnableStreaming = true;
18-
await sqlBulkCopy.WriteToServerAsync(reader);
19-
```
117+
[MIT](LICENSE) -- Copyright (c) 2018 Dimo Terziev

0 commit comments

Comments
 (0)