Skip to content

Commit 48e892c

Browse files
authored
feat: public interface IReadOnlyDataRow to handle wrappers around DataRow (#252)
1 parent 505eccb commit 48e892c

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

Expressif.Testing/ContextTest.cs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Expressif.Values;
22
using Expressif.Values.Special;
3+
using NUnit.Framework.Constraints;
34
using System.Data;
45

56
namespace Expressif.Testing;
@@ -220,7 +221,7 @@ public void CurrentObjectName_DictionaryWithUnavailableKey_ThrowsException()
220221
}
221222

222223
[Test]
223-
public void CurrentObjectName_ObjectWithUnavailableProperty_ThrowsException()
224+
public void CurrentObjectName_AnonymousObjectWithUnavailableProperty_ThrowsException()
224225
{
225226
var context = new Context();
226227
context.CurrentObject.Set(new { foo = 123 });
@@ -232,7 +233,38 @@ public void CurrentObjectName_ObjectWithUnavailableProperty_ThrowsException()
232233
Assert.Multiple(() =>
233234
{
234235
Assert.That(() => context.CurrentObject["foo"], Throws.Nothing);
235-
Assert.That(() => context.CurrentObject["bar"], Throws.TypeOf<ArgumentOutOfRangeException>());
236+
Assert.That(() => context.CurrentObject["bar"], Throws.TypeOf<ArgumentException>()
237+
.With.Message.EqualTo("Cannot find a property named 'bar' in the object of type '<>f__AnonymousType1`1'."));
238+
});
239+
}
240+
241+
private record ObjectTest(string Name) { }
242+
[Test]
243+
public void CurrentObjectName_ObjectWithUnavailableProperty_ThrowsException()
244+
{
245+
var context = new Context();
246+
context.CurrentObject.Set(new ObjectTest("foo"));
247+
Assert.That(context.CurrentObject.Contains("Bar"), Is.False);
248+
Assert.Multiple(() =>
249+
{
250+
Assert.That(() => context.CurrentObject["Bar"], Throws.TypeOf<ArgumentException>()
251+
.With.Message.EqualTo("Cannot find a property named 'Bar' in the object of type 'ObjectTest'."));
252+
});
253+
}
254+
255+
[Test]
256+
public void CurrentObjectName_ObjectNull_ThrowsException()
257+
{
258+
var context = new Context();
259+
context.CurrentObject.Set(null);
260+
Assert.Multiple(() =>
261+
{
262+
Assert.That(context.CurrentObject.Contains("bar"), Is.False);
263+
});
264+
Assert.Multiple(() =>
265+
{
266+
Assert.That(() => context.CurrentObject["bar"], Throws.TypeOf<ArgumentException>()
267+
.With.Message.EqualTo("Cannot find a property named 'bar' in the object of type 'null'."));
236268
});
237269
}
238270

@@ -416,4 +448,66 @@ public void CurrentObjectValue_Any_CorrectResult()
416448
context.CurrentObject.Set(new List<int>() { 123, 456 });
417449
Assert.That(context.CurrentObject.Value, Is.AssignableTo<IList<int>>());
418450
}
451+
452+
private class DataRowWrapper(DataRow row) : IReadOnlyDataRow
453+
{
454+
private DataRow Row { get; } = row;
455+
456+
public object? this[string columnName] => Row[columnName];
457+
458+
public object? this[int index] => Row[index];
459+
460+
public int ColumnsCount => Row.Table.Columns.Count;
461+
462+
public bool ContainsColumn(string columnName) => Row.Table.Columns.Contains(columnName);
463+
}
464+
465+
[Test]
466+
public void CurrentObjectIndex_IReadOnlyDataRowWithUnavailableColumn_ThrowsException()
467+
{
468+
var dt = new DataTable();
469+
dt.Columns.Add("foo", typeof(int));
470+
var row = dt.NewRow();
471+
row.ItemArray = new object[] { 123 };
472+
dt.Rows.Add(row);
473+
var wrapper = new DataRowWrapper(row);
474+
475+
var context = new Context();
476+
context.CurrentObject.Set(wrapper);
477+
Assert.Multiple(() =>
478+
{
479+
Assert.That(context.CurrentObject.Contains(0), Is.True);
480+
Assert.That(context.CurrentObject.Contains(1), Is.False);
481+
});
482+
Assert.Multiple(() =>
483+
{
484+
Assert.That(() => context.CurrentObject[0], Throws.Nothing);
485+
Assert.That(() => context.CurrentObject[1], Throws.TypeOf<ArgumentOutOfRangeException>());
486+
});
487+
}
488+
489+
[Test]
490+
public void CurrentObjectName_IReadOnlyDataRowWithExistingColumn_ValueReturned()
491+
{
492+
var dt = new DataTable();
493+
dt.Columns.Add("foo", typeof(int));
494+
dt.Columns.Add("bar", typeof(object));
495+
var row = dt.NewRow();
496+
row.ItemArray = new object[] { 123, 456 };
497+
dt.Rows.Add(row);
498+
var wrapper = new DataRowWrapper(row);
499+
500+
var context = new Context();
501+
context.CurrentObject.Set(wrapper);
502+
Assert.Multiple(() =>
503+
{
504+
Assert.That(context.CurrentObject.Contains("foo"), Is.True);
505+
Assert.That(context.CurrentObject.Contains("bar"), Is.True);
506+
});
507+
Assert.Multiple(() =>
508+
{
509+
Assert.That(context.CurrentObject["foo"], Is.EqualTo(123));
510+
Assert.That(context.CurrentObject["bar"], Is.EqualTo(456));
511+
});
512+
}
419513
}

Expressif/Values/ContextObject.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class ContextObject
1414
public object? Value { get; private set; }
1515
private PropertyInfo[]? Cache { get; set; }
1616

17-
public void Set(object value)
17+
public void Set(object? value)
1818
{
19-
if (Value == null || Value.GetType() != value.GetType())
19+
if (value == null || Value == null || Value.GetType() != value.GetType())
2020
Cache = null;
2121
Value = value;
2222
}
@@ -25,6 +25,7 @@ public bool Contains(string name)
2525
=> Value switch
2626
{
2727
DataRow row => row.Table.Columns.Contains(name),
28+
IReadOnlyDataRow row => row.ContainsColumn(name),
2829
IDictionary dico => dico.Contains(name),
2930
IList => throw new NotNameableContextObjectException(Value),
3031
_ => TryRetrieveObjectProperty(name, out var _),
@@ -37,15 +38,16 @@ public object? this[string name]
3738
return Value switch
3839
{
3940
DataRow row => row.Table.Columns.Contains(name) ? row[name] : throw new ArgumentOutOfRangeException(name),
40-
IDictionary dico => dico.Contains(name) ? dico[name] : throw new ArgumentOutOfRangeException(name),
41+
IReadOnlyDataRow row => row.ContainsColumn(name) ? row[name] : throw new ArgumentOutOfRangeException(name),
42+
IDictionary dico => dico.Contains(name) ? dico[name] : throw new ArgumentOutOfRangeException(name),
4143
IList => throw new NotNameableContextObjectException(Value),
4244
_ => retrieveObjectProperty(name),
4345
};
4446

4547
object? retrieveObjectProperty(string name)
4648
=> TryRetrieveObjectProperty(name, out var value)
4749
? value
48-
: throw new ArgumentOutOfRangeException(name);
50+
: throw new ArgumentException($"Cannot find a property named '{name}' in the object of type '{Value?.GetType().Name ?? "null"}'.");
4951
}
5052
}
5153

@@ -60,6 +62,7 @@ public bool Contains(int index)
6062
=> Value switch
6163
{
6264
DataRow row => index < row.Table.Columns.Count,
65+
IReadOnlyDataRow row => index < row.ColumnsCount,
6366
IList list => index < list.Count,
6467
_ => throw new NotIndexableContextObjectException(Value)
6568
};
@@ -71,6 +74,7 @@ public object? this[int index]
7174
return Value switch
7275
{
7376
DataRow row => index < row.Table.Columns.Count ? row[index] : throw new ArgumentOutOfRangeException(index.ToString()),
77+
IReadOnlyDataRow row => index < row.ColumnsCount ? row[index] : throw new ArgumentOutOfRangeException(index.ToString()),
7478
IList list => list[index],
7579
_ => throw new NotIndexableContextObjectException(Value)
7680
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.CompilerServices;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace Expressif.Values;
9+
public interface IReadOnlyDataRow
10+
{
11+
int ColumnsCount { get; }
12+
bool ContainsColumn(string columnName);
13+
object? this[string columnName] { get; }
14+
object? this[int index] { get; }
15+
}

0 commit comments

Comments
 (0)