* initial net5.0 tweaks; also fix current IDE warnings
* lib updates and fixes
* more cleanup
* fix snafu
* skip locals init
* disable xunit shadow copy in tests
* test records
* explore scenario that currently works re #1571
Let's get the first version of this going. If you want to try locally:
1. Start the servers (ensuring you have no conflicting port usage - shut down locals if so):
```bash
cd /tests/
docker-compose up
```
Run tests:
```bash
cd <repo root>
dotnet test Build.csproj
```
One of several moves to make this something we can dev on all platforms and build on Actions for v3 onwards.
This leaves the main projects in the root, creating the following:
```
benchmarks\
Dapper.Tests.Performance
Directory.Build.props
tests\
Dapper.Tests
Dapper.Tests.Contrib
Directory.Build.props
Directory.Build.targets
```
Next step is getting `tests\docker-compose.yml` spun up for testing the various providers on GitHub Actions, but I'm having issues with this loaner laptop and the Docker restore from backup being in a screwy state.
This normalizes all the cases of trying to set null to non-null things and giving a better exception when it happens. Today when a user does something like this:
```cs
conn.QueryFirstOrDefault<int>("Select null;");
```
...we'll get a null ref in the `(T)val;` portion of the deserialization pass. It's not very intuitive as to what's gone wrong. The same is true for value tuples and all `Query*<T>()` methods. However, we handle this in a much friendlier way in the generated type deserializers (already, in master) with a message like:
> Error parsing column 1 (Foo=bar - String)
This gives the type we _actually_ got for the column and the position it was in. This PR improves the case for anything from `Query<int>` to `QueryFirst<int>`, etc. We could be more intuitive _if we passed the expected type in_ (which we don't today via the IL Path), or if we can come up with a generic error message along the lines of "... - use a nullable type (e.g. `int?` instead of `int`) if null is expected.". Or something like that - thoughts?
We're using `DataException` because that's what we're already doing on type deserialization in the default paths. That's the generic exception type that'll always work (it's not always a cast or parsing exception...it could be either)...and since it's already in use, sticking with it here.
Overall changes:
- DRYs up `QueryRowImpl<T>` and `QueryRowAsync<T>` (single row paths) - into `ReadRow<T>`
- DRYs up value setting code between `Query<T>`, `QueryAsync<T>`, `QueryRowImpl<T>`, `QueryRowAsync<T>` - into `GetValue<T>`
- When trying to set a null to a non-nullable type, it'll throw a much better exception.
- Adds tests to ensure these error messages are consistent.
Theoretical breaking change:
- Someone's doing this and catching null ref exceptions or string format exceptions. If that's the case, we'll change their exception expectations here - I think in net we should still do this.
Related to:
- #567
- #1421
- #1440
### TODO
- [ ] ValueTuple support - these are handled in yet another path and still throw a different casting error. Note: they won't throw a null error because that's a column and we won't set it - unless `.ApplyNullValues` is set in settings.
### Benchmarks:
```ini
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.208 (2004/?/20H1)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.201
[Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
ShortRun : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
```
#### Before (3 runs)
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 117.4 us | 1.62 us | 2.46 us | 3.6250 | - | - | 11.39 KB |
| Dapper | 'Query<T> (buffered)' | Post | 119.4 us | 0.58 us | 0.88 us | 1.8750 | 0.8750 | - | 11.65 KB |
| Dapper | QueryFirstOrDefault<T> | Post | 120.7 us | 0.78 us | 1.30 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 125.0 us | 2.62 us | 4.41 us | 2.5000 | 1.0000 | 0.2500 | 11.73 KB |
| Dapper | 'Contrib Get<T>' | Post | 127.8 us | 3.19 us | 4.82 us | 2.5000 | 1.0000 | 0.2500 | 12.29 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 160.8 us | 1.19 us | 2.28 us | 2.0000 | 1.0000 | - | 11.77 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 170.6 us | 1.71 us | 2.58 us | 2.0000 | 1.0000 | - | 11.81 KB |
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 114.5 us | 1.81 us | 2.73 us | 3.6250 | - | - | 11.39 KB |
| Dapper | QueryFirstOrDefault<T> | Post | 117.9 us | 0.93 us | 1.56 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 121.3 us | 1.52 us | 2.55 us | 2.5000 | 1.0000 | 0.2500 | 11.73 KB |
| Dapper | 'Query<T> (buffered)' | Post | 122.7 us | 1.09 us | 2.08 us | 2.0000 | 1.0000 | - | 11.65 KB |
| Dapper | 'Contrib Get<T>' | Post | 127.9 us | 1.40 us | 2.12 us | 2.5000 | 0.7500 | 0.2500 | 12.29 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 166.9 us | 1.02 us | 1.71 us | 2.0000 | 1.0000 | - | 11.77 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 170.1 us | 2.19 us | 3.31 us | 2.0000 | 1.0000 | - | 11.81 KB |
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 115.5 us | 3.14 us | 4.75 us | 3.6250 | - | - | 11.39 KB |
| Dapper | QueryFirstOrDefault<T> | Post | 121.5 us | 4.29 us | 7.21 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<T> (buffered)' | Post | 123.9 us | 2.16 us | 3.27 us | 2.0000 | 1.0000 | - | 11.65 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 127.0 us | 3.65 us | 5.52 us | 2.3750 | 0.8750 | 0.3750 | 11.73 KB |
| Dapper | 'Contrib Get<T>' | Post | 130.0 us | 3.18 us | 4.80 us | 2.5000 | 0.7500 | 0.2500 | 12.29 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 166.7 us | 1.39 us | 2.33 us | 2.7500 | 1.0000 | 0.2500 | 11.81 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 170.6 us | 2.18 us | 3.29 us | 2.2500 | 1.0000 | 0.2500 | 11.77 KB |
#### After (3 runs):
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 113.2 us | 0.45 us | 0.75 us | 3.6250 | - | - | 11.39 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 118.7 us | 1.97 us | 2.98 us | 1.8750 | 0.8750 | - | 11.72 KB |
| Dapper | QueryFirstOrDefault<T> | Post | 119.6 us | 1.12 us | 1.88 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<T> (buffered)' | Post | 121.8 us | 4.16 us | 6.28 us | 2.5000 | 1.0000 | 0.2500 | 11.64 KB |
| Dapper | 'Contrib Get<T>' | Post | 124.0 us | 1.21 us | 2.04 us | 2.0000 | 1.0000 | - | 12.28 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 162.4 us | 1.95 us | 3.27 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 173.0 us | 2.68 us | 4.50 us | 2.5000 | 1.0000 | 0.2500 | 11.8 KB |
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 114.1 us | 1.42 us | 2.15 us | 3.6250 | - | - | 11.39 KB |
| Dapper | QueryFirstOrDefault<T> | Post | 120.4 us | 1.23 us | 1.87 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 121.4 us | 3.90 us | 5.90 us | 1.8750 | 0.8750 | - | 11.72 KB |
| Dapper | 'Query<T> (buffered)' | Post | 125.7 us | 1.65 us | 2.50 us | 2.0000 | 1.0000 | - | 11.64 KB |
| Dapper | 'Contrib Get<T>' | Post | 132.1 us | 3.01 us | 4.55 us | 2.2500 | 0.7500 | - | 12.28 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 162.1 us | 2.29 us | 3.85 us | 2.7500 | 1.0000 | 0.2500 | 11.8 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 166.9 us | 2.19 us | 3.31 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB |
| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:|
| Dapper | QueryFirstOrDefault<T> | Post | 114.1 us | 2.80 us | 4.23 us | 1.8750 | 0.8750 | - | 11.35 KB |
| Dapper | 'Query<T> (buffered)' | Post | 115.5 us | 1.30 us | 1.97 us | 2.6250 | 1.0000 | 0.3750 | 11.64 KB |
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 120.8 us | 2.99 us | 4.53 us | 3.6250 | - | - | 11.39 KB |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 123.7 us | 3.61 us | 5.46 us | 2.0000 | 1.0000 | - | 11.72 KB |
| Dapper | 'Contrib Get<T>' | Post | 129.2 us | 2.79 us | 4.22 us | 2.0000 | 1.0000 | - | 12.28 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 163.0 us | 3.67 us | 6.17 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 167.2 us | 0.73 us | 1.10 us | 2.2500 | 1.0000 | - | 11.8 KB |
...in short: it's within the jitter on this laptop and no difference is discernable. It's worth looking at for performance because these are hot paths. I want to de-dupe the repeated code here, but given the hot path nature whether it inlines is important.
Simplifying and improve build speed.
- Decreases builds from 8-13 minutes down to ~2 minutes (while running more tests)
- Moves to SQL Server 2019 on AppVeyor
- Moves the 2 longest running tests with params to `[FactLongRunning]`
- Bumps tests up to `netcoreapp3.1`
- Also fixes .Contrib tests
- Moves to `Build.csproj`
- Simplifies `build.ps1`
- Removed defunct `build.sh`
- Nukes `IsAppveyor` (moves to environmental variable overloads)
- Builds now work in Linux/macOS
I add update `Set` method and unit test.
e.g:
```C#
var sb = new SqlBuilder()
.Set("vip = @vip", new { vip })
.Set("updatetime = @updatetime", new { updatetime })
.Where("id = @id", new { id })
;
var template = sb.AddTemplate("update #Users /**set**/ /**where**/");
connection.Execute(template.RawSql, template.Parameters);
```
RawSql will generate :
```
update #Users Set vip = @vip , updatetime = @updatetime\n WHERE id = @id\n
```
using Dapper Execute in sqlserver it'll reqeust by below sql command
```
exec sp_executesql N'update #Users Set vip = @vip , updatetime = @updatetime
WHERE id = @id
',N'@vip bit,@updatetime datetime,@id int',@vip=1,@updatetime='2020-01-01 00:00:00',@id=1
```
Co-authored-by: Nick Craver <nrcraver@gmail.com>
Co-authored-by: Nick Craver <craver@stackoverflow.com>
`SingleOrDefault<(int, int)?>(query)` always returned null because the IsValueTuple did not handle nullable types and thus the `GenerateValueTupleDeserializer` method was never called.
I've added test for the case when the query does not return something (the tuple should be null) and when it does return something. Additionally added tests for the edge-case where you select 8 or 15 element tuples to make sure nothing broke there.
The comment suggested that more than the number specified in InListStringSplitCount and it would start to use string_split. However, it is greater-than-or-equal-to (GTE) the number specified by InListStringSplitCount.
* - enable deterministic build
- lib updates for core packages
* set source root if needed
* Restore VS test runner
xUnit won't run in VS without it :)
Co-authored-by: Nick Craver <craver@stackoverflow.com>