VerySimpleData
First of all, you can find the repository for VerySimpleData here.
I’m sure you’re familiar with how the compiler will bark at you if you make a typo and end up calling a method or setting a property that doesn’t exist. That’s static typing at work making sure that everything your code is talking about actually does exist. The dynamic type allows you to sidestep this and by-pass what would normally be resolved at compile-time and instead have it resolved dynamically at run-time. This means that you now can call methods or set property that do not exist, as long as you’re using a dynamic type. Let's start with an example of how to use VerySimpleData to highlight this.
1 2 | var database = Database.WithNamedConnectionString("connectionString"); var results = database.MyStoredProcedure(Param: "something", AnotherParam: "something-else"); |
In line one, the connection string is supplied and a dynamic object is returned which will act as the database context. In line two, I am calling a stored procedure called "MyStoredProcedure" and passing it two parameters called "Param" and "AnotherParam"; if I wanted to execute a stored procedure with a different name and/or parameters, I would only need to change the method's name and the names of the parameters that are being supplied:
var results = database.AnotherStoredProcedure(DifferentParam: "something-different");
Looking Under the Hood:
The entry point to VerySimpleData is through one of the static methods on the Database class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static class Database { public static dynamic WithNamedConnectionString(string name) { var connectionStringSettings = ConfigurationManager.ConnectionStrings[name]; if (connectionStringSettings == null) { throw new ArgumentException("No connection string found for: " + name); } return WithConnectionString(connectionStringSettings.ConnectionString); } public static dynamic WithConnectionString(string connectionString) { IDatabase database = new SqlServerDatabase(connectionString); var dynamicContext = new DatabaseContext(database); return dynamicContext; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class DatabaseContext : DynamicObject { private readonly IDatabase Database; public DatabaseContext(IDatabase database) { Database = database; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (binder.CallInfo.ArgumentCount != args.Length) { result = false; return false; } var procedureName = binder.Name; var parameters = binder.CallInfo.ArgumentNames.Zip(args, (s, i) => new { s, i }) .ToDictionary(item => item.s, item => item.i); result = Database.ExecuteStoredProcedure(procedureName, parameters); return true; } } |
An instance of DatabaseContext is returned from the Database static class as a dynamic type when either WithNamedConnectionString() or WithConnectionString() is called. The key to this class is that it inherits from DynamicObject. DynamicObject provides the ability to interact with any of the members that are executed/set on the dynamic type by overriding the base implementation:
My implementation assumes that the name of the invoked member will match the stored procedure’s name (line 19) and that the names of the arguments will match the procedure’s parameters (lines 20-21). Calling the ExecuteStoredProcudure method (line 23) fires off the ADO.NET code that talks to the database and it should be no surprise that this returns a dynamic object as the result could be a single value, a single record, a set of records or nothing at all.
If there’s only one row and multiple columns, this will be returned as a VerySimpleDataRecord:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public class VerySimpleDataRecord : DynamicObject { private readonly Dictionary<string, object> _data; public VerySimpleDataRecord() :this(new Dictionary<string, object>()) { } public VerySimpleDataRecord(Dictionary<string, object> data) { _data = data; } public override bool TryGetMember(GetMemberBinder binder, out object result) { if (_data.ContainsKey(binder.Name)) { result = _data[binder.Name]; return true; } return base.TryGetMember(binder, out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { _data[binder.Name] = value; return true; } public int ColumnCount { get { return _data.Count; } } } |
This is constructed using a dictionary collection where the key-value pairs comprise of the column name and the data that was held in that column. The TryGetMember override is invoked when performing a property get operation on the dynamic object; if there is a key in the dictionary collection that matches the property’s name, it is returned.
The final scenario is where there are multiple rows with one or more columns. In this case, a collection of data will be returned that is packaged in a way that make it enumerable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public class VerySimpleDataRecordSet : DynamicObject, IEnumerable { private readonly IList<VerySimpleDataRecord> _data; public VerySimpleDataRecordSet() : this(new List<VerySimpleDataRecord>()) { } public VerySimpleDataRecordSet(IList<VerySimpleDataRecord> data) { _data = data; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { if (indexes.Length == 1 && indexes[0].GetType() == typeof(int)) { result = _data[(int)indexes[0]]; return true; } result = false; return false; } public int Count { get { return _data.Count; } } public IEnumerator GetEnumerator() { return _data.GetEnumerator(); } } |
A VerySimpleDataRecordSet is initialized by supplying a collection of VerySimpleDataRecords. Like the dictionary collection in the VerySimpleDataRecord, this is stored internally. VerySimpleDataRecordSet also inherits from DynamicObject that provides the TryGetIndex override. When this is invoked, the supplied index value is first validated and if it passes, the appropriate VerySimpleDataRecord is retrieved from the internal collection and returned. The ability to enumerate the VerySimpleDataRecordSet is achieved by implementing IEnumerable: the GetEnumerator method exposes the internal collection’s IEnumerator.
I have only touched a few of the method overrides that are available through DynamicObject mainly to do with getting and returning data. There are other operations to do with setting values and performing conversions that would be worth exploring.