Skip to content

Commit d7c3a1c

Browse files
committed
- added documentation
1 parent bb8f3cc commit d7c3a1c

1 file changed

Lines changed: 381 additions & 0 deletions

File tree

README.md

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,384 @@ Shuttle.Core.Data
22
=================
33

44
Provides a thin abstraction over ADO.NET.
5+
6+
The following may be found on the [documentation site](http://shuttle.github.io/shuttle-core/overview-data/):
7+
8+
# Overview
9+
10+
The `Shuttle.Core.Data` package provides a thin abstraction over ADO.NET by making use of the `DbProviderFactories`. Even though it provides object/relational mapping mechasnims it is in no way an ORM.
11+
12+
# IDatabaseContextFactory
13+
14+
As per usual, in order to access a database, we need a database connection. A database connection is represented by a `IDatabaseContext` instance that may be obtained by using an instance of an `IDatabaseContextFactory` implementation.
15+
16+
The `DatabaseContextFactory` implmentation makes use of an `IDbConnectionFactory` implementation, that creates a `System.Data.IDbConnection` by using the provider name and connection string, an `IDbCommandFactory` that creates a `System.Data.IDbCommand` by using `IDbConnection` instance. The `DatabaseContextFactory` also requires an instance of a `IDatabaseContextCache` that stores connections and is assigned to the `DatabaseContext` in order to obtain the active connection.
17+
18+
~~~ c#
19+
var factory = DatabaseContextFactory.Default();
20+
21+
using (var context = factory.Create("connectionStringName"))
22+
{
23+
// database interaction
24+
}
25+
26+
using (var context = factory
27+
.Create("System.Data.SqlClient",
28+
"Data Source=.\sqlexpress;Initial Catalog=Shuttle;Integrated Security=SSPI"))
29+
{
30+
// database interaction
31+
}
32+
33+
using (var context = factory.Create(existingIDbConnection))
34+
{
35+
// database interaction
36+
}
37+
~~~
38+
39+
# IConfiguredDatabaseContextFactory
40+
41+
You can pre-configure your database context factory using this interface. If you typically connect to only one data source this may be helpful:
42+
43+
~~~ c#
44+
IDatabaseContext Create();
45+
46+
void ConfigureWith(string connectionStringName);
47+
void ConfigureWith(string providerName, string connectionString);
48+
void ConfigureWith(IDbConnection dbConnection);
49+
~~~
50+
51+
# IDatabaseGateway
52+
53+
The `DatabaseGateway` is used to execute `IQuery` instances in order return data from, or make changes to, the underlying data store. If there is no active open `IDatabaseContext` returned by the `DatabaseContext.Current` and `InvalidOperationException` will be thrown.
54+
55+
The following section each describe the methods available in the `IDatabaseGateway` interface.
56+
57+
## GetReaderUsing
58+
59+
~~~ c#
60+
IDataReader GetReaderUsing(IQuery query);
61+
~~~
62+
63+
Returns an `IDataReader` instance for the given `select` statement:
64+
65+
~~~ c#
66+
var factory = DatabaseContextFactory.Default();
67+
var gateway = new DatabaseGateway();
68+
69+
using (var context = factory.Create("connectionStringName"))
70+
{
71+
var reader = gateway.GetReaderUsing(RawQuery.Create("select Id, Username from dbo.Member"));
72+
}
73+
~~~
74+
75+
## ExecuteUsing
76+
77+
~~~ c#
78+
int ExecuteUsing(IQuery query);
79+
~~~
80+
81+
Executes the given query and returns the number of rows affected:
82+
83+
~~~ c#
84+
var factory = DatabaseContextFactory.Default();
85+
var gateway = new DatabaseGateway();
86+
87+
using (var context = factory.Create("connectionStringName"))
88+
{
89+
gateway.ExecuteUsing(RawQuery.Create("delete from dbo.Member where Username = 'mr.resistor'"));
90+
}
91+
~~~
92+
93+
## GetScalarUsing<T>
94+
95+
~~~ c#
96+
T GetScalarUsing<T>(IQuery query);
97+
~~~
98+
99+
Get the scalar value returned by the `select` query. The query shoud return only one value (scalar):
100+
101+
~~~ c#
102+
var factory = DatabaseContextFactory.Default();
103+
var gateway = new DatabaseGateway();
104+
105+
using (var context = factory.Create("connectionStringName"))
106+
{
107+
var username = gateway.GetScalarUsing<string>(
108+
RawQuery.Create("select Username from dbo.Member where Id = 10")
109+
);
110+
111+
var id = gateway.GetScalarUsing<int>(
112+
RawQuery.Create("select Id from dbo.Member where Username = 'mr.resistor'")
113+
);
114+
}
115+
~~~
116+
117+
## GetDataTableFor
118+
119+
~~~ c#
120+
DataTable GetDataTableFor(IQuery query);
121+
~~~
122+
123+
Returns a `DataTable` containing the rows returned for the given `select` statement.
124+
125+
~~~ c#
126+
var factory = DatabaseContextFactory.Default();
127+
var gateway = new DatabaseGateway();
128+
129+
using (var context = factory.Create("connectionStringName"))
130+
{
131+
var table = gateway.GetDataTableFor(RawQuery.Create("select Id, Username from dbo.Member"));
132+
}
133+
~~~
134+
135+
## GetRowsUsing
136+
137+
~~~ c#
138+
IEnumerable<DataRow> GetRowsUsing(IQuery query);
139+
~~~
140+
141+
Returns an enumerable containing the `DataRow` instances returned for a `select` query:
142+
143+
~~~ c#
144+
var factory = DatabaseContextFactory.Default();
145+
var gateway = new DatabaseGateway();
146+
147+
using (var context = factory.Create("connectionStringName"))
148+
{
149+
var rows = gateway.GetRowsUsing(RawQuery.Create("select Id, Username from dbo.Member"));
150+
}
151+
~~~
152+
153+
154+
## GetSingleRowUsing
155+
156+
~~~ c#
157+
DataRow GetSingleRowUsing(IQuery query);
158+
~~~
159+
160+
Returns a single `DataRow` containing the values returned for a `select` statement that returns exactly one row:
161+
162+
~~~ c#
163+
var factory = DatabaseContextFactory.Default();
164+
var gateway = new DatabaseGateway();
165+
166+
using (var context = factory.Create("connectionStringName"))
167+
{
168+
var row = gateway.GetSingleRowUsing(
169+
RawQuery.Create("select Id, Username, EMail, DateActivated from dbo.Member where Id = 10")
170+
);
171+
}
172+
~~~
173+
174+
# IDataRepository<T>
175+
176+
An `IDataRepository<T>` implementation is responsible for returning a hydrated object. To this end you make use of the `DataReposity<T>` class that takes a `IDatabaseGateway` instance along with a `IDataRowMapper<T>` used to create the hydrated instance.
177+
178+
The following methods can be used to interact with your object type.
179+
180+
## FetchAllUsing
181+
182+
~~~ c#
183+
IEnumerable<T> FetchAllUsing(IQuery query);
184+
~~~
185+
186+
Uses the `select` clause represented by the `IQuery` instance to create a list of objects of type `T`. The `select` clause will need to select all the required columns and will, typically, return more than one instance.
187+
188+
## FetchItemUsing
189+
190+
~~~ c#
191+
T FetchItemUsing(IQuery query);
192+
~~~
193+
194+
Returns a single object instance of type `T` that is hydrated using the data returned from the `select` clause represented by the `IQuery` instance.
195+
196+
## FetchMappedRowsUsing
197+
198+
~~~ c#
199+
IEnumerable<MappedRow<T>> FetchMappedRowsUsing(IQuery query);
200+
~~~
201+
202+
This is similar to the `FetchAllUsing` method but instead returns a list of `MappedRow<T>` instances. Uses the `select` clause represented by the `IQuery` instance to create a list of `MappedRow` instances of type `T`. The `select` clause will need to select all the required columns and will, typically, return more than one instance.
203+
204+
## FetchMappedRowUsing
205+
206+
~~~ c#
207+
MappedRow<T> FetchMappedRowUsing(IQuery query);
208+
~~~
209+
210+
Similar to the `FetchItemUsing` method but instead return a `MappedRow<T>` instance that is hydrated using the data returned from the `select` clause represented by the `IQuery` instance.
211+
212+
## Contains
213+
214+
~~~ c#
215+
bool Contains(IQuery query);
216+
~~~
217+
218+
Returns `true` is the `IQuery` instance `select` clause returns an `int` scalar that equals `1`; else returns `false`.
219+
220+
# IQuery
221+
222+
An `IQuery` represent a database query that can be executed against the relevant database type. There is only one method that needs to be implemented:
223+
224+
~~~ c#
225+
void Prepare(IDbCommand command);
226+
~~~
227+
228+
This should ensure that the given `IDbCommand` is configured for execution by setting the relvant command attributes and parameters.
229+
230+
# IQueryParameter: IQuery
231+
232+
An `IQueryParameter` inherits the `IQuery` interface and extends it by allowing you to add parameters to a query by specifying an `IMappedColumn` (see below) instance along with the value for the parameter.
233+
234+
There are two implementations of this interface.
235+
236+
## RawQuery
237+
238+
The `RawQuery` enables you to create any query using the native language structure:
239+
240+
~~~ c#
241+
var query = RawQuery.Create("select UserName from dbo.Member where Id = @Id")
242+
.AddParameterValue(new MappedColumn<Guid>("Id", DbType.Guid),
243+
new Guid('{75208260-CF93-454E-95EC-FE1903F3664E}'));
244+
~~~
245+
246+
## ProcedureQuery
247+
248+
The `ProcedureQuery` is used to execute a stored procedure:
249+
250+
~~~ c#
251+
var query = ProcedureQuery.Create("uspMemberById")
252+
.AddParameterValue(new MappedColumn<Guid>("Id", DbType.Guid),
253+
new Guid('{75208260-CF93-454E-95EC-FE1903F3664E}'));
254+
~~~
255+
256+
# MappedColumn
257+
258+
Typically you would not want to create a `MappedColumn` each time you need it and these are also quite fixed. A column mapping can, therefore, by defined statically:
259+
260+
~~~ c#
261+
using System;
262+
using System.Data;
263+
using Shuttle.Core.Data;
264+
265+
namespace Shuttle.Ordering.DataAccess
266+
{
267+
public class OrderColumns
268+
{
269+
public static readonly MappedColumn<Guid> Id =
270+
new MappedColumn<Guid>("Id", DbType.Guid);
271+
272+
public static readonly MappedColumn<string> OrderNumber =
273+
new MappedColumn<string>("OrderNumber", DbType.String, 20);
274+
275+
public static readonly MappedColumn<string> OrderDate =
276+
new MappedColumn<string>("OrderDate", DbType.DateTime);
277+
278+
public static readonly MappedColumn<string> CustomerName =
279+
new MappedColumn<string>("CustomerName", DbType.String, 65);
280+
281+
public static readonly MappedColumn<string> CustomerEMail =
282+
new MappedColumn<string>("CustomerEMail", DbType.String, 130);
283+
}
284+
}
285+
~~~
286+
287+
There are quite a few options that you can set on the `MappedColumn` in order to represent your column properly.
288+
289+
## MapFrom
290+
291+
~~~ c#
292+
public T MapFrom(DataRow row)
293+
~~~
294+
295+
This will return the typed value of the specified column as contained in the passed-in `DataRow`.
296+
297+
# IDataRowMapper<T>
298+
299+
You use this interface to implement a mapper for a `DataRow` that will result in an object of type `T`:
300+
301+
~~~ c#
302+
using System.Data;
303+
using Shuttle.Core.Data;
304+
using Shuttle.Process.Custom.Server.Domain;
305+
306+
namespace Shuttle.ProcessManagement
307+
{
308+
public class OrderProcessMapper : IDataRowMapper<OrderProcess>
309+
{
310+
public MappedRow<OrderProcess> Map(DataRow row)
311+
{
312+
var result = new OrderProcess(OrderProcessColumns.Id.MapFrom(row))
313+
{
314+
CustomerName = OrderProcessColumns.CustomerName.MapFrom(row),
315+
CustomerEMail = OrderProcessColumns.CustomerEMail.MapFrom(row),
316+
OrderId = OrderProcessColumns.OrderId.MapFrom(row),
317+
InvoiceId = OrderProcessColumns.InvoiceId.MapFrom(row),
318+
DateRegistered = OrderProcessColumns.DateRegistered.MapFrom(row),
319+
OrderNumber = OrderProcessColumns.OrderNumber.MapFrom(row)
320+
};
321+
322+
return new MappedRow<OrderProcess>(row, result);
323+
}
324+
}
325+
}
326+
~~~
327+
328+
# MappedRow
329+
330+
A `MappedRow` instance contains bother a `DataRow` and the object that the `DataRow` mapped to.
331+
332+
This may be useful in situation where the `DataRow` contains more information that is available on the object. An example may be an `OrderLine` where the `DataRow` contains the `OrderId` column but the `OrderLine` object does not. In order to still be able to make that association it is useful to have both available.
333+
334+
# IAssembler
335+
336+
An `IAssembler` implementation is used to create multiple mappings with as few calls as possible. An example may be where we perform two `select` queries; one to get 3 orders and another to get the order lines belonging to those 3 orders.
337+
338+
> `select OrderId, OrderNumber, OrderDate from dbo.Order where OrderId in (2, 6, 44)`
339+
340+
| Order Id | Order Number | Order Date |
341+
| --- | --- | --- |
342+
| 2 | ORD-002 | 14 Feb 2016 |
343+
| 6 | ORD-006 | 24 Mar 2016 |
344+
| 44 | ORD-044 | 4 Apr 2016 |
345+
346+
> `select OrderId, Product, Quantity from dbo.OrderLine where OrderId in (2, 6, 44)`
347+
348+
| Order Id | Product | Quantity |
349+
| --- | --- | --- |
350+
| 2 | Red Socks | 2 |
351+
| 2 | Blue Socks | 3 |
352+
| 6 | Sports Towel | 1 |
353+
| 6 | Squash Racquet | 1 |
354+
| 6 | Squash Ball | 3 |
355+
| 44 | Vaughn's DDD Book | 1 |
356+
| 44 | Shuttle.Sentinel License | 5 |
357+
358+
Using a `MappedData` instance we can keep adding the `MappedRow` instances to the `MappedData` and then have the assembler return the three `Order` aggregates:
359+
360+
~~~ c#
361+
public class OrderAssembler : IAssembler<Order>
362+
{
363+
public IEnumerable<Order> Assemble(MappedData data)
364+
{
365+
var result = new List<Order>();
366+
367+
foreach (var orderRow in data.MappedRows<Order>())
368+
{
369+
var order = orderRow.Result;
370+
371+
foreach (var orderLineRow in data.MappedRows<OrderLine>())
372+
{
373+
if (orderLineRow.Row["OrderId"].Equals(order.OrderId))
374+
{
375+
order.AddLine(orderLineRow.Result);
376+
}
377+
}
378+
379+
result.Add(order);
380+
}
381+
382+
return result;
383+
}
384+
}
385+
~~~

0 commit comments

Comments
 (0)