Skip to content

Commit

Permalink
Added WithDefaultValue
Browse files Browse the repository at this point in the history
  • Loading branch information
seesharper committed Nov 19, 2024
1 parent 6e8cb6a commit eb5868d
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 11 deletions.
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 @@ public static class ValueConverter
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 @@ public static void RegisterReadDelegate<T>(Func<IDataRecord, int, T> convertFunc
ReadDelegates.AddOrUpdate(typeof(T), type => convertFunction, (type, del) => convertFunction);
}

public static void RegisterDefaultValue<T>(T defaultValue)
{
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 @@ internal static bool CanConvert(Type type)
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

0 comments on commit eb5868d

Please sign in to comment.