diff --git a/Mapper.sln b/Mapper.sln index 8711a41..b50de80 100644 --- a/Mapper.sln +++ b/Mapper.sln @@ -7,6 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mapper", "Mapper\Mapper.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mapper.UnitTests", "Mapper.UnitTests\Mapper.UnitTests.csproj", "{AD2B32F7-7DF4-4B7F-9115-BDB6ACA20EB1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{09E1E16A-03B8-421F-AEB7-BB64F8A83D7A}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Mapper/CommandExtension.cs b/Mapper/CommandExtension.cs index 4029e95..6db133b 100644 --- a/Mapper/CommandExtension.cs +++ b/Mapper/CommandExtension.cs @@ -4,7 +4,6 @@ using System.Data; using System.Data.SqlClient; using System.Diagnostics.Contracts; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; @@ -12,13 +11,13 @@ namespace Mapper { - public static class CommandExtension + public static class DbCommandExtensions { private static readonly MostlyReadDictionary Methods = new MostlyReadDictionary(); /// Executes the reading exactly one item /// when zero values read or more than one value can be read - public static T Single(this IDbCommand cmd) + public static T ReadSingle(this IDbCommand cmd) { Contract.Requires(cmd != null); Contract.Ensures(Contract.Result() != null); @@ -30,7 +29,7 @@ public static T Single(this IDbCommand cmd) /// Executes the reading exactly one item /// when zero values read or more than one value can be read - public static async Task SingleAsync(this SqlCommand cmd) + public static async Task ReadSingleAsync(this SqlCommand cmd) { Contract.Requires(cmd != null); Contract.Ensures(Contract.Result() != null); @@ -42,7 +41,7 @@ public static async Task SingleAsync(this SqlCommand cmd) /// Executes the reading one item /// Returns the default vaue of T if no values be read, i.e may return null - public static T SingleOrDefault(this IDbCommand cmd) + public static T ReadSingleOrDefault(this IDbCommand cmd) { Contract.Requires(cmd != null); using (var reader = cmd.ExecuteReader()) @@ -53,7 +52,7 @@ public static T SingleOrDefault(this IDbCommand cmd) /// Executes the reading one item /// Returns the default vaue of T if no values be read, i.e may return null - public static async Task SingleOrDefaultAsync(this SqlCommand cmd) + public static async Task ReadSingleOrDefaultAsync(this SqlCommand cmd) { Contract.Requires(cmd != null); using (var reader = await cmd.ExecuteReaderAsync()) @@ -63,7 +62,7 @@ public static async Task SingleOrDefaultAsync(this SqlCommand cmd) } /// Executes the and reads all the records into a list - public static List ToList(this IDbCommand cmd) + public static List ReadList(this IDbCommand cmd) { Contract.Requires(cmd != null); Contract.Ensures(Contract.Result>() != null); @@ -74,7 +73,7 @@ public static List ToList(this IDbCommand cmd) } /// Executes the and reads all the records into a list - public static async Task> ToListAsync(this SqlCommand cmd) + public static async Task> ReadListAsync(this SqlCommand cmd) { Contract.Requires(cmd != null); Contract.Ensures(Contract.Result>() != null); @@ -85,7 +84,7 @@ public static async Task> ToListAsync(this SqlCommand cmd) } /// Executes the and reads all the records into a dictionary, using the supplied to generate the key - public static Dictionary ToDictionary(this IDbCommand cmd, Func keyFunc) + public static Dictionary ReadDictionary(this IDbCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); @@ -97,7 +96,7 @@ public static Dictionary ToDictionary(this IDbComman } /// Executes the and reads all the records into a dictionary, using the supplied to generate the key - public static async Task> ToDictionaryAsync(this SqlCommand cmd, Func keyFunc) + public static async Task> ReadDictionaryAsync(this SqlCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); @@ -109,7 +108,7 @@ public static async Task> ToDictionaryAsyncExecutes the and reads all the records in a lookup, grouped by key, using the supplied to generate the key - public static HashLookup ToLookup(this IDbCommand cmd, Func keyFunc) + public static HashLookup ReadLookup(this IDbCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); @@ -121,7 +120,7 @@ public static HashLookup ToLookup(this IDbCommand cm } /// Executes the and reads all the records in a lookup, grouped by key, using the supplied to generate the key - public static async Task> ToLookupAsync(this SqlCommand cmd, Func keyFunc) + public static async Task> ReadLookupAsync(this SqlCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); diff --git a/Mapper/ConnectionExtension.cs b/Mapper/ConnectionExtension.cs index 0d9a6ab..243c326 100644 --- a/Mapper/ConnectionExtension.cs +++ b/Mapper/ConnectionExtension.cs @@ -22,7 +22,7 @@ public static T QuerySingle(this IDbConnection cnn, string sql, object parame using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.Single(); + return cmd.ReadSingle(); } } @@ -35,7 +35,7 @@ public static Task QuerySingleAsync(this SqlConnection cnn, string sql, ob using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.SingleAsync(); + return cmd.ReadSingleAsync(); } } @@ -47,7 +47,7 @@ public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, obje using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.SingleOrDefault(); + return cmd.ReadSingleOrDefault(); } } @@ -59,7 +59,7 @@ public static Task QuerySingleOrDefaultAsync(this SqlConnection cnn, strin using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.SingleOrDefaultAsync(); + return cmd.ReadSingleOrDefaultAsync(); } } @@ -73,7 +73,7 @@ public static List QueryList(this IDbConnection cnn, string sql, object pa using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.ToList(); + return cmd.ReadList(); } } @@ -87,7 +87,7 @@ public static Task> QueryListAsync(this SqlConnection cnn, string sql using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.ToListAsync(); + return cmd.ReadListAsync(); } } @@ -102,7 +102,7 @@ public static Dictionary QueryDictionary(this IDbCon using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, null); - return cmd.ToDictionary(keyFunc); + return cmd.ReadDictionary(keyFunc); } } @@ -117,7 +117,7 @@ public static Task> QueryDictionaryAsync( using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, null); - return cmd.ToDictionaryAsync(keyFunc); + return cmd.ReadDictionaryAsync(keyFunc); } } @@ -133,7 +133,7 @@ public static Dictionary QueryDictionary(this IDbCon using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.ToDictionary(keyFunc); + return cmd.ReadDictionary(keyFunc); } } @@ -149,7 +149,7 @@ public static Task> QueryDictionaryAsync( using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.ToDictionaryAsync(keyFunc); + return cmd.ReadDictionaryAsync(keyFunc); } } @@ -164,7 +164,7 @@ public static ILookup QueryLookup(this IDbConnection using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, null); - return cmd.ToLookup(keyFunc); + return cmd.ReadLookup(keyFunc); } } @@ -180,7 +180,7 @@ public static ILookup QueryLookup(this IDbConnection using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); - return cmd.ToLookup(keyFunc); + return cmd.ReadLookup(keyFunc); } } @@ -195,7 +195,7 @@ public static async Task> QueryLookupAsync> QueryLookupAsync Mapper - 1.0.1.2 + 1.0.1.3 BusterWood A convention-based object cloner, object-object mapper (like AutoMapper), IDataReader to object mapper, object to IDbDataParameter mapper, etc. https://github.com/busterwood/mapper diff --git a/Mapper/Properties/AssemblyInfo.cs b/Mapper/Properties/AssemblyInfo.cs index 9f35ff2..bb50550 100644 --- a/Mapper/Properties/AssemblyInfo.cs +++ b/Mapper/Properties/AssemblyInfo.cs @@ -12,6 +12,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.1.2")] +[assembly: AssemblyFileVersion("1.0.1.3")] [assembly: InternalsVisibleTo("Mapper.UnitTests")] \ No newline at end of file diff --git a/README.md b/README.md index f4ef793..2329793 100644 --- a/README.md +++ b/README.md @@ -3,32 +3,94 @@ ## Cloning -`Mapper` contains an extension method for all objects called `Clone` which performs a *shallow* clone. The object being clone *must* have a parameterless contructor, then all public properties and fields are copied. +`Mapper` contains an extension method for all objects called `Clone` which performs a *shallow* clone. The type being cloned *must* have a parameterless contructor, then all public properties and fields are copied. +`Mapper` can also clone sequences of object via the `MapSome()` extension which takes a `IEnumerable` and returns an `IEnumerable`. ## Mapping -You can copy an object of one type to another type using the `Map()` extension method. +You can copy an object of one type to another type using the `Map()` extension method. The type being mapped to *must* have a parameterless contructor, then all readable public properties (and fields) of the source type are copied to properties (or fields) of the target type. + +A property (or field) types must be compatible in some sense, the following list the type compatibility rules: + +| Source Type | Target Type | +|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| +| Any numeric type or enum | Any numeric type or any enum | +| `Nullable` where T is any numeric type or enum | any numeric type or any enum (`default(T)` is used as the value if value is null | +| `Nullable` where T is any numeric type or enum | `Nullable` where T is any numeric type or enum | +| any type other | type must match or be [assignable](https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom(v=vs.110).aspx) | + +## Name compatibility + +For `Map`, `MapSome` and all the data mappings, the following rules apply when looking for candidate names: +1. the source name (case insensitive) +2. if the name ends with 'ID' then try the name without 'ID' (case insensitive) +3. the name above names with underscores removed (case insensitive) + +For example, if the source name is `ORDER_ID` then the following names would be considered (shown in perference order): +1. ORDER_ID +2. ORDER_ +3. ORDERID +4. ORDER ## Data extensions -The headline examples is much like Dapper, but methods are strongly typed: +The headline examples is much like Dapper, but methods have stronly typed return values: Select a list: ``` List list = connection.QueryList("select * from dbo.[Order] where order_id = @OrderId", new { OrderId = 123 }); ``` -Select a dictionary keyed by the primary key +Select a dictionary keyed by the primary key: ``` Dictionary list = connection.QueryDictionary("select * from dbo.[Order] where status = @Status", new { Status = 1 }, order => order.Id); ``` -Select a key to multiple value `ILookup` +Select a key to multiple value `ILookup`: ``` ILookup list = connection.QueryLookup("select * from dbo.[Order] where order_date > @OrderDate", new { OrderDate = new DateTime(2016, 8, 1) }, order => order.Status); ``` -## Composability -TODO: `IDataReader` extensions -TODO: `IDataCommand` extensions -TODO: `SqlDataRecord` extensions +## Data Composability + +`Mapper` has a series of extension methods for ADO.Net types: + +### IDataReader methods +`IDataReader` has the following extension methods: +* `Single()` for reading exactly one row +* `SingleOrDefault()` for reading zero or one rows +* `ToList()` for reading all records into a `List` +* `ToDictinary(Func keyFunc)` for reading all records into a `Dictinary` using the supplied function to get work out the key. Note that the key must be unique. +* `ToLookup(Func keyFunc)` for reading all records into a `ILookup` using the supplied function to get work out the key. Each key may have multiple values. + +### SqlDataReader async methods + +Additionally `SqlDataReader` has the same set of methods as `IDataReader` but with `Async` suffix. + +## IDbCommand methods + +`Mapper` adds `AddParameters(object parameters)` extension method to `IDbCommand`. `AddParameters` will add a `IDataParameter` to the commands `Parameters` collection for each readable public property (and field) of `parameters`, setting the type and value. + +For convenience `Mapper` adds the following extension method to `IDbCommand`: +* `ReadSingle()` for exeucting the command and reading exactly one row +* `ReadSingleOrDefault()` for exeucting the command and reading zero or one rows +* `ReadList()` for exeucting the command and reading all records into a `List` +* `ReadDictinary(Func keyFunc)` for exeucting the command and reading all records into a `Dictinary` using the supplied function to get work out the key. Note that the key must be unique. +* `ReadLookup(Func keyFunc)` for exeucting the command and reading all records into a `ILookup` using the supplied function to get work out the key. Each key may have multiple values. + +### SqlCommand async methods + +Additionally `SqlCommand` has the same set of methods as `IDbDataReader` but with `Async` suffix. + +### IDbConnetion methods + +For convenience `Mapper` adds the following extension method to `IDbConnection`: +* `QuerySingle()` for executing the command and reading exactly one row +* `QuerySingleOrDefault()` for executing the command and reading zero or one rows +* `Queryist()` for executing the command and reading all records into a `List` +* `QueryDictinary(Func keyFunc)` for executing the command and reading all records into a `Dictinary` using the supplied function to get work out the key. Note that the key must be unique. +* `QueryLookup(Func keyFunc)` for executing the command and reading all records into a `ILookup` using the supplied function to get work out the key. Each key may have multiple values. + +### SqlDataRecord methods + +`Mapper` has a extension method `ToTableType()` for converting a source `IEnumerable` into an `IEnumerable` such that it can be passed as a [table valued parameter](https://msdn.microsoft.com/en-us/library/bb675163(v=vs.110).aspx) to SQL Server.