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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
run: dotnet tool install dotnet-script --global

- name: Install dotnet-ilverify
run: dotnet tool install dotnet-ilverify --global
run: dotnet tool install dotnet-ilverify --global --version 8.0.0

- name: Run build script
run: dotnet-script build/build.csx
Expand Down
51 changes: 42 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,14 @@ ON
c.CustomerId = @CustomerID
```

| CustomerId|CompanyName|OrderId|OrderDate
| ----------|-----------|-------|---------
|ALFKI|Alfreds Futterkiste|10643|1997-08-25
|ALFKI|Alfreds Futterkiste|10692|1997-10-03
|ALFKI|Alfreds Futterkiste|10702|1997-10-13
|ALFKI|Alfreds Futterkiste|10835|1998-01-15
|ALFKI|Alfreds Futterkiste|10952|1998-03-16
|ALFKI|Alfreds Futterkiste|11011|1998-04-09
| CustomerId | CompanyName | OrderId | OrderDate |
| ---------- | ------------------- | ------- | ---------- |
| ALFKI | Alfreds Futterkiste | 10643 | 1997-08-25 |
| ALFKI | Alfreds Futterkiste | 10692 | 1997-10-03 |
| ALFKI | Alfreds Futterkiste | 10702 | 1997-10-13 |
| ALFKI | Alfreds Futterkiste | 10835 | 1998-01-15 |
| ALFKI | Alfreds Futterkiste | 10952 | 1998-03-16 |
| ALFKI | Alfreds Futterkiste | 11011 | 1998-04-09 |

As we can see from the result we now have six rows. One for each *Order* and the *Customer* columns are duplicated for each *Order*.

Expand Down Expand Up @@ -550,9 +550,42 @@ This instructs **DbReader** to use our custom read delegate whenever it encounte
The *Guid* also needs to be converted back into a byte array when passing a *Guid* value as a parameter to a query.

```
DbReaderOptions.WhenPassing<Guid>().Use((parameter, guid) => parameter.Value = guid.ToByArray());
DbReaderOptions.WhenPassing<Guid>().Use((parameter, guid) => parameter.Value = guid.ToByArray()
```

> Note that the conversion function is only invoked if the value from the database is NOT `DBNull`

### Default values

When using a custom conversion function (WhenReading) it is possible to define a default for the value to be used when value from the database is `DBNull`.

Say that we have a class with a property of type `CustomValueType`

```c#

public class CustomValueType
{
public CustomValueType(int value)
{
Value = value;
}

public int Value { get; }
}

public class MyClass
{
public CustomValueType SomeProperty { get; set; }
}
```

When the property `SomeProperty` we can specify what to return in the case of the field (SomeProperty) being `DBNull` from the `IDataRecord`

```C#
DbReaderOptions.WhenReading<CustomValueType>().Use((dr, i) => new CustomValueType(dr.GetInt32(i))).WithDefaultValue(new CustomValueType(42));
```


## Simple Types

Sometimes we just want to get a list of a simple types such as `string` or maybe an `integer`
Expand Down
23 changes: 23 additions & 0 deletions src/DbReader.Tests/InstanceReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,29 @@ public void ShouldHandleNullValuesInNavigationChain()
result.Children.ShouldBeEmpty();
}

[Fact]
public void ShouldUseDefaultValue()
{
var dataRecord = new { Id = 1, Property = DBNull.Value }.ToDataRecord();
DbReaderOptions.WhenReading<CustomValueType>().Use((dr, i) => new CustomValueType(dr.GetInt32(i))).WithDefaultValue(new CustomValueType(42));
var reader = GetReader<ClassWithProperty<CustomValueType>>();
var instance = reader.Read(dataRecord, string.Empty);
instance.Property.Value.ShouldBe(42);
}


public class CustomValueType
{
public CustomValueType(int value)
{
Value = value;
}

public int Value { get; }
}



[Fact]
public void Test()
{
Expand Down
8 changes: 8 additions & 0 deletions src/DbReader/Construction/PropertyReaderMethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ private void EmitPropertySetter(ILGenerator il, PropertyInfo property, int prope
EmitGetValue(il, propertyIndex, getMethod, property.PropertyType);
EmitCallPropertySetterMethod(il, property);
il.MarkLabel(endLabel);
if (ValueConverter.HasDefaultValue(property.PropertyType))
{
var openGenericGetDefaultValueMethod = typeof(ValueConverter).GetMethod(nameof(ValueConverter.GetDefaultValue), BindingFlags.Static | BindingFlags.NonPublic);
var getDefaultValueMethod = openGenericGetDefaultValueMethod.MakeGenericMethod(property.PropertyType);
LoadInstance(il, instanceVariable);
il.Emit(OpCodes.Call, getDefaultValueMethod);
EmitCallPropertySetterMethod(il, property);
}
}
}
}
21 changes: 19 additions & 2 deletions src/DbReader/ReadDelegate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@ public class ReadDelegate<TProperty>
/// Specifies the <paramref name="readFunction"/> to be used to read a value of type <typeparamref name="TProperty"/>
/// </summary>
/// <param name="readFunction">The function to be used to read the value.</param>
public void Use(Func<IDataRecord, int, TProperty> readFunction)
public DefaultValue<TProperty> Use(Func<IDataRecord, int, TProperty> readFunction)
{
ValueConverter.RegisterReadDelegate(readFunction);
ValueConverter.RegisterReadDelegate(readFunction);
return new DefaultValue<TProperty>();
}
}

/// <summary>
/// Specifies the default value to be used when the value from the database is <see cref="DBNull"/>.
/// </summary>
/// <typeparam name="TProperty">The type of property for which to specify a default value.</typeparam>
public class DefaultValue<TProperty>
{
/// <summary>
/// Specifies the <paramref name="defaultValue"/> to be used when the value from the database is <see cref="DBNull"/>.
/// </summary>
/// <param name="defaultValue">The value to be used when the value from the database is <see cref="DBNull"/>.</param>
public void WithDefaultValue(TProperty defaultValue)
{
ValueConverter.RegisterDefaultValue(defaultValue);
}
}
}
24 changes: 24 additions & 0 deletions src/DbReader/ValueConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
private static readonly ConcurrentDictionary<Type, Delegate> ReadDelegates =
new ConcurrentDictionary<Type, Delegate>();

private static readonly ConcurrentDictionary<Type, object> DefaultValues = new ConcurrentDictionary<Type, object>();

/// <summary>
/// Registers a function delegate that creates a value of <typeparamref name="T"/> from an <see cref="IDataRecord"/>
/// at the specified ordinal (column index).
Expand All @@ -47,6 +49,12 @@
ReadDelegates.AddOrUpdate(typeof(T), type => convertFunction, (type, del) => convertFunction);
}

public static void RegisterDefaultValue<T>(T defaultValue)

Check warning on line 52 in src/DbReader/ValueConverter.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'ValueConverter.RegisterDefaultValue<T>(T)'

Check warning on line 52 in src/DbReader/ValueConverter.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'ValueConverter.RegisterDefaultValue<T>(T)'
{
DefaultValues.AddOrUpdate(typeof(T), type => defaultValue, (type, value) => defaultValue);
}


/// <summary>
/// Determines if the given <paramref name="type"/> can be converted.
/// </summary>
Expand All @@ -57,6 +65,22 @@
return ReadDelegates.ContainsKey(type);
}

/// <summary>
/// Determines if the given <paramref name="type"/> has a default value to be used when the value from the database is null.
/// </summary>
/// <param name="type">The type to be checked for a default value.</param>
/// <returns>true, if there is a default value registration for the given type, otherwise, false.</returns>
internal static bool HasDefaultValue(Type type)
{
return DefaultValues.ContainsKey(type);
}

internal static T GetDefaultValue<T>()
{
return (T)DefaultValues[typeof(T)];
}


/// <summary>
/// Converts the value from the <paramref name="dataRecord"/> at the given <paramref name="ordinal"/>
/// to an instance of <typeparamref name="T"/>.
Expand Down
Loading