Commit ee98aa53 authored by David Chell's avatar David Chell

Merge branch 'master' of https://github.com/SamSaffron/dapper-dot-net

Conflicts:
	Dapper.Contrib/SqlMapperExtensions.cs
parents 390ba5b4 e9d167e9
...@@ -2,4 +2,9 @@ ...@@ -2,4 +2,9 @@
bin/ bin/
obj/ obj/
/*.user /*.user
_Resharper* _Resharper*
\ No newline at end of file .hgtags
NuGet.exe
*.user
*.nupkg
.docstats
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
<ProjectGuid>{B26305D8-3A89-4D68-A981-9BBF378B81FA}</ProjectGuid> <ProjectGuid>{B26305D8-3A89-4D68-A981-9BBF378B81FA}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper_NET35</RootNamespace> <RootNamespace>Dapper</RootNamespace>
<AssemblyName>Dapper NET35</AssemblyName> <AssemblyName>Dapper</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
</PropertyGroup> </PropertyGroup>
...@@ -36,7 +36,6 @@ ...@@ -36,7 +36,6 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Dapper\Properties\AssemblyInfo.cs"> <Compile Include="..\Dapper\Properties\AssemblyInfo.cs">
......
...@@ -65,6 +65,10 @@ ...@@ -65,6 +65,10 @@
<Project>{C2FC4DF5-C8D1-4EA8-8E0C-85A3793EB0BB}</Project> <Project>{C2FC4DF5-C8D1-4EA8-8E0C-85A3793EB0BB}</Project>
<Name>Dapper.Contrib</Name> <Name>Dapper.Contrib</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Dapper.SqlBuilder\Dapper.SqlBuilder.csproj">
<Project>{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}</Project>
<Name>Dapper.SqlBuilder</Name>
</ProjectReference>
<ProjectReference Include="..\Dapper\Dapper.csproj"> <ProjectReference Include="..\Dapper\Dapper.csproj">
<Project>{DAF737E1-05B5-4189-A5AA-DAC6233B64D7}</Project> <Project>{DAF737E1-05B5-4189-A5AA-DAC6233B64D7}</Project>
<Name>Dapper</Name> <Name>Dapper</Name>
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System;
using Dapper;
namespace Dapper.Contrib.Tests namespace Dapper.Contrib.Tests
...@@ -100,5 +103,52 @@ public void InsertGetUpdate() ...@@ -100,5 +103,52 @@ public void InsertGetUpdate()
connection.Update(notrackedUser).IsEqualTo(false); //returns false, user not found connection.Update(notrackedUser).IsEqualTo(false); //returns false, user not found
} }
} }
public void BuilderSelectClause()
{
using (var connection = GetOpenConnection())
{
var rand = new Random(8675309);
var data = new List<User>();
for (int i = 0; i < 100; i++)
{
var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() };
data.Add(nU);
nU.Id = (int)connection.Insert<User>(nU);
}
var builder = new SqlBuilder();
var justId = builder.AddTemplate("SELECT /**select**/ FROM Users");
var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users");
builder.Select("Id");
var ids = connection.Query<int>(justId.RawSql, justId.Parameters);
var users = connection.Query<User>(all.RawSql, all.Parameters);
foreach (var u in data)
{
if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select");
if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select");
}
}
}
public void BuilderTemplateWOComposition()
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new {age = 5});
if (template.RawSql == null) throw new Exception("RawSql null");
if (template.Parameters == null) throw new Exception("Parameters null");
using (var connection = GetOpenConnection())
{
connection.Insert(new User { Age = 5, Name = "Testy McTestington" });
if (connection.Query<int>(template.RawSql, template.Parameters).Single() != 1)
throw new Exception("Query failed");
}
}
} }
} }
...@@ -29,9 +29,11 @@ public interface IProxy ...@@ -29,9 +29,11 @@ public interface IProxy
private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
{ {
if (KeyProperties.ContainsKey(type.TypeHandle))
IEnumerable<PropertyInfo> pi;
if (KeyProperties.TryGetValue(type.TypeHandle,out pi))
{ {
return KeyProperties[type.TypeHandle]; return pi;
} }
var allProperties = TypePropertiesCache(type); var allProperties = TypePropertiesCache(type);
...@@ -51,9 +53,10 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) ...@@ -51,9 +53,10 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
} }
private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type) private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
{ {
if (TypeProperties.ContainsKey(type.TypeHandle)) IEnumerable<PropertyInfo> pis;
if (TypeProperties.TryGetValue(type.TypeHandle, out pis))
{ {
return TypeProperties[type.TypeHandle]; return pis;
} }
var properties = type.GetProperties().Where(IsWriteable); var properties = type.GetProperties().Where(IsWriteable);
...@@ -325,9 +328,10 @@ public static T GetInterfaceProxy<T>() ...@@ -325,9 +328,10 @@ public static T GetInterfaceProxy<T>()
{ {
Type typeOfT = typeof(T); Type typeOfT = typeof(T);
if (TypeCache.ContainsKey(typeOfT)) object k;
if (TypeCache.TryGetValue(typeOfT, out k))
{ {
return (T)TypeCache[typeOfT]; return (T)k;
} }
var assemblyBuilder = GetAsmBuilder(typeOfT.Name); var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper.Rainbow</RootNamespace>
<AssemblyName>Dapper.Rainbow</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Database.cs" />
<Compile Include="Snapshotter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapper\Dapper.csproj">
<Project>{DAF737E1-05B5-4189-A5AA-DAC6233B64D7}</Project>
<Name>Dapper</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Dapper.Rainbow.nuspec" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
<?xml version="1.0"?>
<package >
<metadata>
<id>Dapper.Rainbow</id>
<version>0.1.1</version>
<title>Dapper.Rainbow</title>
<authors>Sam Saffron</authors>
<owners>Sam Saffron</owners>
<licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
<projectUrl>http://code.google.com/p/dapper-dot-net/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>
Trivial micro-orm implemented on Dapper, provides with CRUD helpers.
</description>
<summary>
A demo is available at https://gist.github.com/1599013 .
The implementation was extracted from http://data.stackexchange.com source at: http://code.google.com/p/stack-exchange-data-explorer/ .
Data explorer uses "Rainbow" exclusively for all database access.
</summary>
<releaseNotes>* version 0.1 </releaseNotes>
<copyright>Copyright Sam Saffron 2012</copyright>
<tags>orm dapper micro-orm</tags>
<dependencies>
<dependency id="Dapper" version="1.8" />
</dependencies>
</metadata>
<files>
<file src="bin\Release\Dapper.Rainbow.dll" target="lib\net40" />
</files>
</package>
\ No newline at end of file
/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: http://code.google.com/p/dapper-dot-net/
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using Dapper;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text;
using System.Data.Common;
using System.Diagnostics;
using System.Reflection.Emit;
namespace Dapper
{
/// <summary>
/// A container for a database, assumes all the tables have an Id column named Id
/// </summary>
/// <typeparam name="TDatabase"></typeparam>
public abstract class Database<TDatabase> : IDisposable where TDatabase : Database<TDatabase>, new()
{
public class Table<T>
{
Database<TDatabase> database;
string tableName;
string likelyTableName;
public Table(Database<TDatabase> database, string likelyTableName)
{
this.database = database;
this.likelyTableName = likelyTableName;
}
public string TableName
{
get
{
tableName = tableName ?? database.DetermineTableName<T>(likelyTableName);
return tableName;
}
}
/// <summary>
/// Insert a row into the db
/// </summary>
/// <param name="data">Either DynamicParameters or an anonymous type or concrete type</param>
/// <returns></returns>
public int? Insert(dynamic data)
{
var o = (object)data;
List<string> paramNames = GetParamNames(o);
string cols = string.Join(",", paramNames);
string cols_params = string.Join(",", paramNames.Select(p => "@" + p));
var sql = "set nocount on insert " + TableName + " (" + cols + ") values (" + cols_params + ") select cast(scope_identity() as int)";
return database.Query<int?>(sql, o).Single();
}
/// <summary>
/// Update a record in the DB
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <returns></returns>
public int Update(int id, dynamic data)
{
List<string> paramNames = GetParamNames((object)data);
var builder = new StringBuilder();
builder.Append("update [").Append(TableName).Append("] set ");
builder.AppendLine(string.Join(",", paramNames.Where(n => n != "Id").Select(p => p + "= @" + p)));
builder.Append("where Id = @Id");
DynamicParameters parameters = new DynamicParameters(data);
parameters.Add("Id", id);
return database.Execute(builder.ToString(), parameters);
}
/// <summary>
/// Delete a record for the DB
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Delete(int id)
{
return database.Execute("delete " + TableName + " where Id = @id", new { id }) > 0;
}
/// <summary>
/// Grab a record with a particular Id from the DB
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public T Get(int id)
{
return database.Query<T>("select * from " + TableName + " where id = @id", new { id }).FirstOrDefault();
}
public T First()
{
return database.Query<T>("select top 1 * from " + TableName).FirstOrDefault();
}
public IEnumerable<T> All()
{
return database.Query<T>("select * from " + TableName);
}
static ConcurrentDictionary<Type, List<string>> paramNameCache = new ConcurrentDictionary<Type, List<string>>();
private static List<string> GetParamNames(object o)
{
if (o is DynamicParameters)
{
return (o as DynamicParameters).ParameterNames.ToList();
}
List<string> paramNames;
if (!paramNameCache.TryGetValue(o.GetType(), out paramNames))
{
paramNames = new List<string>();
foreach (var prop in o.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public))
{
paramNames.Add(prop.Name);
}
paramNameCache[o.GetType()] = paramNames;
}
return paramNames;
}
}
DbConnection connection;
int commandTimeout;
DbTransaction transaction;
public static TDatabase Init(DbConnection connection, int commandTimeout)
{
TDatabase db = new TDatabase();
db.InitDatabase(connection, commandTimeout);
return db;
}
private static Action<Database<TDatabase>> tableConstructor;
private void InitDatabase(DbConnection connection, int commandTimeout)
{
this.connection = connection;
this.commandTimeout = commandTimeout;
if (tableConstructor == null)
{
tableConstructor = CreateTableConstructor();
}
tableConstructor(this);
}
public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted)
{
transaction = connection.BeginTransaction(isolation);
}
public void CommitTransaction()
{
transaction.Commit();
transaction = null;
}
public void RollbackTransaction()
{
transaction.Rollback();
transaction = null;
}
protected Action<Database<TDatabase>> CreateTableConstructor()
{
var dm = new DynamicMethod("ConstructInstances", null, new Type[] { typeof(Database<TDatabase>) }, true);
var il = dm.GetILGenerator();
var setters = GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Table<>))
.Select(p => Tuple.Create(
p.GetSetMethod(true),
p.PropertyType.GetConstructor(new Type[] { typeof(Database<TDatabase>), typeof(string) }),
p.Name,
p.DeclaringType
));
foreach (var setter in setters)
{
il.Emit(OpCodes.Ldarg_0);
// [db]
il.Emit(OpCodes.Ldstr, setter.Item3);
// [db, likelyname]
il.Emit(OpCodes.Newobj, setter.Item2);
// [table]
var table = il.DeclareLocal(setter.Item2.DeclaringType);
il.Emit(OpCodes.Stloc, table);
// []
il.Emit(OpCodes.Ldarg_0);
// [db]
il.Emit(OpCodes.Castclass, setter.Item4);
// [db cast to container]
il.Emit(OpCodes.Ldloc, table);
// [db cast to container, table]
il.Emit(OpCodes.Callvirt, setter.Item1);
// []
}
il.Emit(OpCodes.Ret);
return (Action<Database<TDatabase>>)dm.CreateDelegate(typeof(Action<Database<TDatabase>>));
}
static ConcurrentDictionary<Type, string> tableNameMap = new ConcurrentDictionary<Type, string>();
private string DetermineTableName<T>(string likelyTableName)
{
string name;
if (!tableNameMap.TryGetValue(typeof(T), out name))
{
name = likelyTableName;
if (!TableExists(name))
{
name = typeof(T).Name;
}
tableNameMap[typeof(T)] = name;
}
return name;
}
private bool TableExists(string name)
{
return connection.Query("select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = @name", new { name }, transaction: transaction).Count() == 1;
}
public int Execute(string sql, dynamic param = null)
{
return SqlMapper.Execute(connection, sql, param as object, transaction, commandTimeout: this.commandTimeout);
}
public IEnumerable<T> Query<T>(string sql, dynamic param = null, bool buffered = true)
{
return SqlMapper.Query<T>(connection, sql, param as object, transaction, buffered, commandTimeout);
}
public IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
{
return SqlMapper.Query(connection, sql, map, param as object, transaction, buffered, splitOn);
}
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
{
return SqlMapper.Query(connection, sql, map, param as object, transaction, buffered, splitOn);
}
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
{
return SqlMapper.Query(connection, sql, map, param as object, transaction, buffered, splitOn);
}
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
{
return SqlMapper.Query(connection, sql, map, param as object, transaction, buffered, splitOn);
}
public IEnumerable<dynamic> Query(string sql, dynamic param = null, bool buffered = true)
{
return SqlMapper.Query(connection, sql, param as object, transaction, buffered);
}
public Dapper.SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return SqlMapper.QueryMultiple(connection, sql, param, transaction, commandTimeout, commandType);
}
public void Dispose()
{
if (connection.State != ConnectionState.Closed)
{
if (transaction != null)
{
transaction.Rollback();
}
connection.Close();
connection = null;
}
}
}
}
\ No newline at end of file
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Dapper.Rainbow")]
[assembly: AssemblyDescription("I sample mini ORM implementation using Dapper")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dapper.Rainbow")]
[assembly: AssemblyCopyright("Sam Saffron Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e763c106-eef4-4654-afcc-c28fded057e5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")]
/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: http://code.google.com/p/dapper-dot-net/
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Reflection.Emit;
namespace Dapper
{
public static class Snapshotter
{
public static Snapshot<T> Start<T>(T obj)
{
return new Snapshot<T>(obj);
}
public class Snapshot<T>
{
static Func<T, T> cloner;
static Func<T, T, List<Change>> differ;
T memberWiseClone;
T trackedObject;
public Snapshot(T original)
{
memberWiseClone = Clone(original);
trackedObject = original;
}
public class Change
{
public string Name { get; set; }
public object NewValue { get; set; }
}
public DynamicParameters Diff()
{
return Diff(memberWiseClone, trackedObject);
}
private static T Clone(T myObject)
{
cloner = cloner ?? GenerateCloner();
return cloner(myObject);
}
private static DynamicParameters Diff(T original, T current)
{
var dm = new DynamicParameters();
differ = differ ?? GenerateDiffer();
foreach (var pair in differ(original, current))
{
dm.Add(pair.Name, pair.NewValue);
}
return dm;
}
static List<PropertyInfo> RelevantProperties()
{
return typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p =>
p.GetSetMethod() != null &&
p.GetGetMethod() != null &&
(p.PropertyType.IsValueType ||
p.PropertyType == typeof(string) ||
(p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
).ToList();
}
private static bool AreEqual<U>(U first, U second)
{
if (first == null && second == null) return true;
if (first == null && second != null) return false;
return first.Equals(second);
}
private static Func<T, T, List<Change>> GenerateDiffer()
{
var dm = new DynamicMethod("DoDiff", typeof(List<Change>), new Type[] { typeof(T), typeof(T) }, true);
var il = dm.GetILGenerator();
// change list
il.DeclareLocal(typeof(List<Change>));
il.DeclareLocal(typeof(Change));
il.DeclareLocal(typeof(object)); // boxed change
il.Emit(OpCodes.Newobj, typeof(List<Change>).GetConstructor(Type.EmptyTypes));
// [list]
il.Emit(OpCodes.Stloc_0);
foreach (var prop in RelevantProperties())
{
// []
il.Emit(OpCodes.Ldarg_0);
// [original]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod());
// [original prop val]
il.Emit(OpCodes.Ldarg_1);
// [original prop val, current]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod());
// [original prop val, current prop val]
il.Emit(OpCodes.Dup);
// [original prop val, current prop val, current prop val]
if (prop.PropertyType != typeof(string))
{
il.Emit(OpCodes.Box, prop.PropertyType);
// [original prop val, current prop val, current prop val boxed]
}
il.Emit(OpCodes.Stloc_2);
// [original prop val, current prop val]
il.EmitCall(OpCodes.Call, typeof(Snapshot<T>).GetMethod("AreEqual", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(new Type[] { prop.PropertyType }), null);
// [result]
Label skip = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, skip);
// []
il.Emit(OpCodes.Newobj, typeof(Change).GetConstructor(Type.EmptyTypes));
// [change]
il.Emit(OpCodes.Dup);
// [change,change]
il.Emit(OpCodes.Stloc_1);
// [change]
il.Emit(OpCodes.Ldstr, prop.Name);
// [change, name]
il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_Name"));
// []
il.Emit(OpCodes.Ldloc_1);
// [change]
il.Emit(OpCodes.Ldloc_2);
// [change, boxed]
il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_NewValue"));
// []
il.Emit(OpCodes.Ldloc_0);
// [change list]
il.Emit(OpCodes.Ldloc_1);
// [change list, change]
il.Emit(OpCodes.Callvirt, typeof(List<Change>).GetMethod("Add"));
// []
il.MarkLabel(skip);
}
il.Emit(OpCodes.Ldloc_0);
// [change list]
il.Emit(OpCodes.Ret);
return (Func<T, T, List<Change>>)dm.CreateDelegate(typeof(Func<T, T, List<Change>>));
}
// adapted from http://stackoverflow.com/a/966466/17174
private static Func<T, T> GenerateCloner()
{
Delegate myExec = null;
var dm = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
var ctor = typeof(T).GetConstructor(new Type[] { });
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(T));
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);
foreach (var prop in RelevantProperties())
{
il.Emit(OpCodes.Ldloc_0);
// [clone]
il.Emit(OpCodes.Ldarg_0);
// [clone, source]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod());
// [clone, source val]
il.Emit(OpCodes.Callvirt, prop.GetSetMethod());
// []
}
// Load new constructed obj on eval stack -> 1 item on stack
il.Emit(OpCodes.Ldloc_0);
// Return constructed object. --> 0 items on stack
il.Emit(OpCodes.Ret);
myExec = dm.CreateDelegate(typeof(Func<T, T>));
return (Func<T, T>)myExec;
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper.SqlBuilder</RootNamespace>
<AssemblyName>Dapper.SqlBuilder</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SqlBuilder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapper\Dapper.csproj">
<Project>{DAF737E1-05B5-4189-A5AA-DAC6233B64D7}</Project>
<Name>Dapper</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Dapper.SqlBuilder")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dapper.SqlBuilder")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("27491c26-c95d-44e5-b907-30559ef11265")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Dapper
{
public class SqlBuilder
{
Dictionary<string, Clauses> data = new Dictionary<string, Clauses>();
int seq;
class Clause
{
public string Sql { get; set; }
public object Parameters { get; set; }
}
class Clauses : List<Clause>
{
string joiner;
string prefix;
string postfix;
public Clauses(string joiner, string prefix = "", string postfix = "")
{
this.joiner = joiner;
this.prefix = prefix;
this.postfix = postfix;
}
public string ResolveClauses(DynamicParameters p)
{
foreach (var item in this)
{
p.AddDynamicParams(item.Parameters);
}
return prefix + string.Join(joiner, this.Select(c => c.Sql)) + postfix;
}
}
public class Template
{
readonly string sql;
readonly SqlBuilder builder;
readonly object initParams;
int dataSeq = -1; // Unresolved
public Template(SqlBuilder builder, string sql, dynamic parameters)
{
this.initParams = parameters;
this.sql = sql;
this.builder = builder;
}
static System.Text.RegularExpressions.Regex regex =
new System.Text.RegularExpressions.Regex(@"\/\*\*.+\*\*\/", System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.Multiline);
void ResolveSql()
{
if (dataSeq != builder.seq)
{
DynamicParameters p = new DynamicParameters(initParams);
rawSql = sql;
foreach (var pair in builder.data)
{
rawSql = rawSql.Replace("/**" + pair.Key + "**/", pair.Value.ResolveClauses(p));
}
parameters = p;
// replace all that is left with empty
rawSql = regex.Replace(rawSql, "");
dataSeq = builder.seq;
}
}
string rawSql;
object parameters;
public string RawSql { get { ResolveSql(); return rawSql; } }
public object Parameters { get { ResolveSql(); return parameters; } }
}
public SqlBuilder()
{
}
public Template AddTemplate(string sql, dynamic parameters = null)
{
return new Template(this, sql, parameters);
}
void AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "")
{
Clauses clauses;
if (!data.TryGetValue(name, out clauses))
{
clauses = new Clauses(joiner, prefix, postfix);
data[name] = clauses;
}
clauses.Add(new Clause { Sql = sql, Parameters = parameters });
seq++;
}
public SqlBuilder LeftJoin(string sql, dynamic parameters = null)
{
AddClause("leftjoin", sql, parameters, joiner: "\nLEFT JOIN ", prefix: "\nLEFT JOIN ", postfix: "\n");
return this;
}
public SqlBuilder Where(string sql, dynamic parameters = null)
{
AddClause("where", sql, parameters, " AND ", prefix: "WHERE ", postfix: "\n");
return this;
}
public SqlBuilder OrderBy(string sql, dynamic parameters = null)
{
AddClause("orderby", sql, parameters, " , ", prefix: "ORDER BY ", postfix: "\n");
return this;
}
public SqlBuilder Select(string sql, dynamic parameters = null)
{
AddClause("select", sql, parameters, " , ", prefix: "", postfix: "\n");
return this;
}
public SqlBuilder AddParameters(dynamic parameters)
{
AddClause("--parameters", "", parameters, "");
return this;
}
public SqlBuilder Join(string sql, dynamic parameters = null)
{
AddClause("join", sql, parameters, joiner: "\nJOIN ", prefix: "\nJOIN ", postfix: "\n");
return this;
}
public SqlBuilder GroupBy(string sql, dynamic parameters = null)
{
AddClause("groupby", sql, parameters, joiner: " , ", prefix: "\nGROUP BY ", postfix: "\n");
return this;
}
public SqlBuilder Having(string sql, dynamic parameters = null)
{
AddClause("having", sql, parameters, joiner: "\nAND ", prefix: "HAVING ", postfix: "\n");
return this;
}
}
}
...@@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.Contrib.Tests", "Dap ...@@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.Contrib.Tests", "Dap
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperTests NET35", "DapperTests NET35\DapperTests NET35.csproj", "{3BAA9F79-BA0A-4092-B47B-20170DD47989}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperTests NET35", "DapperTests NET35\DapperTests NET35.csproj", "{3BAA9F79-BA0A-4092-B47B-20170DD47989}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -83,6 +87,26 @@ Global ...@@ -83,6 +87,26 @@ Global
{3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|Mixed Platforms.Build.0 = Release|x86 {3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|Mixed Platforms.Build.0 = Release|x86
{3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|x86.ActiveCfg = Release|x86 {3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|x86.ActiveCfg = Release|x86
{3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|x86.Build.0 = Release|x86 {3BAA9F79-BA0A-4092-B47B-20170DD47989}.Release|x86.Build.0 = Release|x86
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Debug|x86.ActiveCfg = Debug|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Release|Any CPU.Build.0 = Release|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BF782EF1-2B0F-42FA-9DD0-928454A94C6D}.Release|x86.ActiveCfg = Release|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Release|Any CPU.Build.0 = Release|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{21BC6EA8-3D10-4CC9-A1B3-9FAD59F7D1BB}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
......
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin\Debug\Dapper.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
...@@ -36,7 +38,6 @@ ...@@ -36,7 +38,6 @@
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="SqlMapper.cs" /> <Compile Include="SqlMapper.cs" />
......
...@@ -32,5 +32,5 @@ ...@@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.6.0.0")] [assembly: AssemblyVersion("1.8.0.0")]
[assembly: AssemblyFileVersion("1.6.0.0")] [assembly: AssemblyFileVersion("1.8.0.0")]
...@@ -19,11 +19,22 @@ ...@@ -19,11 +19,22 @@
namespace Dapper namespace Dapper
{ {
/// <summary>
/// Dapper, a light weight object mapper for ADO.NET
/// </summary>
public static partial class SqlMapper public static partial class SqlMapper
{ {
/// <summary>
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
/// </summary>
public interface IDynamicParameters public interface IDynamicParameters
{ {
void AddParameters(IDbCommand command); /// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
void AddParameters(IDbCommand command, Identity identity);
} }
static Link<Type, Action<IDbCommand, bool>> bindByNameCache; static Link<Type, Action<IDbCommand, bool>> bindByNameCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType) static Action<IDbCommand, bool> GetBindByName(Type commandType)
...@@ -107,13 +118,23 @@ private Link(TKey key, TValue value, Link<TKey, TValue> tail) ...@@ -107,13 +118,23 @@ private Link(TKey key, TValue value, Link<TKey, TValue> tail)
} }
class CacheInfo class CacheInfo
{ {
public object Deserializer { get; set; } public Func<IDataReader, object> Deserializer { get; set; }
public object[] OtherDeserializers { get; set; } public Func<IDataReader, object>[] OtherDeserializers { get; set; }
public Action<IDbCommand, object> ParamReader { get; set; } public Action<IDbCommand, object> ParamReader { get; set; }
private int hitCount; private int hitCount;
public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); }
public void RecordHit() { Interlocked.Increment(ref hitCount); } public void RecordHit() { Interlocked.Increment(ref hitCount); }
} }
/// <summary>
/// Called if the query cache is purged via PurgeQueryCache
/// </summary>
public static event EventHandler QueryCachePurged;
private static void OnQueryCachePurged()
{
var handler = QueryCachePurged;
if (handler != null) handler(null, EventArgs.Empty);
}
#if CSHARP30 #if CSHARP30
private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>(); private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>();
// note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of // note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of
...@@ -126,11 +147,14 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value) ...@@ -126,11 +147,14 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{ {
lock (_queryCache) { return _queryCache.TryGetValue(key, out value); } lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
} }
private static void PurgeQueryCache(Identity key) public static void PurgeQueryCache()
{ {
lock (_queryCache) { _queryCache.Remove(key); } lock (_queryCache)
{
_queryCache.Clear();
}
OnQueryCachePurged();
} }
#else #else
static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>(); static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
private static void SetQueryCache(Identity key, CacheInfo value) private static void SetQueryCache(Identity key, CacheInfo value)
...@@ -155,6 +179,7 @@ private static void CollectCacheGarbage() ...@@ -155,6 +179,7 @@ private static void CollectCacheGarbage()
} }
} }
} }
finally finally
{ {
Interlocked.Exchange(ref collect, 0); Interlocked.Exchange(ref collect, 0);
...@@ -173,18 +198,30 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value) ...@@ -173,18 +198,30 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
value = null; value = null;
return false; return false;
} }
private static void PurgeQueryCache(Identity key)
/// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{ {
CacheInfo info; _queryCache.Clear();
_queryCache.TryRemove(key, out info); OnQueryCachePurged();
} }
/// <summary>
/// Return a count of all the cached queries by dapper
/// </summary>
/// <returns></returns>
public static int GetCachedSQLCount() public static int GetCachedSQLCount()
{ {
return _queryCache.Count; return _queryCache.Count;
} }
/// <summary>
/// Return a list of all the queries cached by dapper
/// </summary>
/// <param name="ignoreHitCountAbove"></param>
/// <returns></returns>
public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
{ {
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
...@@ -192,6 +229,10 @@ public static int GetCachedSQLCount() ...@@ -192,6 +229,10 @@ public static int GetCachedSQLCount()
return data; return data;
} }
/// <summary>
/// Deep diagnostics only: find any hash collisions in the cache
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<int,int>> GetHashCollissions() public static IEnumerable<Tuple<int,int>> GetHashCollissions()
{ {
var counts = new Dictionary<int, int>(); var counts = new Dictionary<int, int>();
...@@ -253,9 +294,9 @@ static SqlMapper() ...@@ -253,9 +294,9 @@ static SqlMapper()
typeMap[typeof(Guid?)] = DbType.Guid; typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime; typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset; typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(System.Data.Linq.Binary)] = DbType.Binary;
} }
private const string LinqBinary = "System.Data.Linq.Binary";
private static DbType LookupDbType(Type type, string name) private static DbType LookupDbType(Type type, string name)
{ {
DbType dbType; DbType dbType;
...@@ -269,6 +310,10 @@ private static DbType LookupDbType(Type type, string name) ...@@ -269,6 +310,10 @@ private static DbType LookupDbType(Type type, string name)
{ {
return dbType; return dbType;
} }
if (type.FullName == LinqBinary)
{
return DbType.Binary;
}
if (typeof(IEnumerable).IsAssignableFrom(type)) if (typeof(IEnumerable).IsAssignableFrom(type))
{ {
// use xml to denote its a list, hacky but will work on any DB // use xml to denote its a list, hacky but will work on any DB
...@@ -279,7 +324,10 @@ private static DbType LookupDbType(Type type, string name) ...@@ -279,7 +324,10 @@ private static DbType LookupDbType(Type type, string name)
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type)); throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
} }
internal class Identity : IEquatable<Identity> /// <summary>
/// Identity of a cached query in Dapper, used for extensability
/// </summary>
public class Identity : IEquatable<Identity>
{ {
internal Identity ForGrid(Type primaryType, int gridIndex) internal Identity ForGrid(Type primaryType, int gridIndex)
{ {
...@@ -290,6 +338,15 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) ...@@ -290,6 +338,15 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex)
{ {
return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex);
} }
/// <summary>
/// Create an identity for use with DynamicParameters, internal use only
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public Identity ForDynamicParameters(Type type)
{
return new Identity(sql, commandType, connectionString, this.type ,type, null, -1);
}
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes) internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) : this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0)
...@@ -320,20 +377,51 @@ private Identity(string sql, CommandType? commandType, string connectionString, ...@@ -320,20 +377,51 @@ private Identity(string sql, CommandType? commandType, string connectionString,
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode()); hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
} }
} }
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return Equals(obj as Identity); return Equals(obj as Identity);
} }
internal readonly string sql; /// <summary>
internal readonly CommandType? commandType; /// The sql
internal readonly int hashCode, gridIndex; /// </summary>
public readonly string sql;
/// <summary>
/// The command type
/// </summary>
public readonly CommandType? commandType;
/// <summary>
///
/// </summary>
public readonly int hashCode, gridIndex;
private readonly Type type; private readonly Type type;
internal readonly string connectionString; /// <summary>
internal readonly Type parametersType; ///
/// </summary>
public readonly string connectionString;
/// <summary>
///
/// </summary>
public readonly Type parametersType;
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return hashCode; return hashCode;
} }
/// <summary>
/// Compare 2 Identity objects
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(Identity other) public bool Equals(Identity other)
{ {
return return
...@@ -414,9 +502,12 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object ...@@ -414,9 +502,12 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object
} }
// nice and simple // nice and simple
identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null); if ((object)param != null)
info = GetCacheInfo(identity); {
return ExecuteCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType); identity = new Identity(sql, commandType, cnn, null, (object) param == null ? null : ((object) param).GetType(), null);
info = GetCacheInfo(identity);
}
return ExecuteCommand(cnn, transaction, sql, (object)param == null ? null : info.ParamReader, (object)param, commandTimeout, commandType);
} }
#if !CSHARP30 #if !CSHARP30
/// <summary> /// <summary>
...@@ -431,7 +522,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -431,7 +522,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
/// <summary> /// <summary>
/// Executes a query, returning the data typed as per T /// Executes a query, returning the data typed as per T
/// </summary> /// </summary>
/// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new <space> get new object</remarks> /// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns> /// </returns>
...@@ -484,36 +575,41 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -484,36 +575,41 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
{ {
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity);
bool clean = true;
try using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType))
{ {
using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType)) using (var reader = cmd.ExecuteReader())
{ {
using (var reader = cmd.ExecuteReader()) Func<Func<IDataReader, object>> cacheDeserializer = () =>
{ {
if (info.Deserializer == null) info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
{ SetQueryCache(identity, info);
info.Deserializer = GetDeserializer<T>(reader, 0, -1, false); return info.Deserializer;
SetQueryCache(identity, info); };
}
var deserializer = (Func<IDataReader, T>)info.Deserializer; if (info.Deserializer == null)
{
cacheDeserializer();
}
while (reader.Read()) var deserializer = info.Deserializer;
while (reader.Read())
{
object next;
try
{ {
clean = false; next = deserializer(reader);
var next = deserializer(reader);
clean = true;
yield return next;
} }
catch (DataException)
{
// give it another shot, in case the underlying schema changed
deserializer = cacheDeserializer();
next = deserializer(reader);
}
yield return (T)next;
} }
}
}
finally
{ // throw away query plan on failure - could
if (!clean)
{
PurgeQueryCache(identity);
} }
} }
} }
...@@ -521,8 +617,9 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -521,8 +617,9 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
/// <summary> /// <summary>
/// Maps a query to objects /// Maps a query to objects
/// </summary> /// </summary>
/// <typeparam name="T">The return type</typeparam> /// <typeparam name="TFirst">The first type in the recordset</typeparam>
/// <typeparam name="U"></typeparam> /// <typeparam name="TSecond">The second type in the recordset</typeparam>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param> /// <param name="cnn"></param>
/// <param name="sql"></param> /// <param name="sql"></param>
/// <param name="map"></param> /// <param name="map"></param>
...@@ -531,6 +628,7 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -531,6 +628,7 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
/// <param name="buffered"></param> /// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param> /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
#if CSHARP30 #if CSHARP30
...@@ -543,6 +641,23 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -543,6 +641,23 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} }
/// <summary>
/// Maps a query to objects
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>( public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
#if CSHARP30 #if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
...@@ -554,6 +669,24 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -554,6 +669,24 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} }
/// <summary>
/// Perform a multi mapping query with 4 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>( public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
#if CSHARP30 #if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
...@@ -565,6 +698,25 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -565,6 +698,25 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} }
#if !CSHARP30 #if !CSHARP30
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
...@@ -578,6 +730,7 @@ class DontMap { } ...@@ -578,6 +730,7 @@ class DontMap { }
return buffered ? results.ToList() : results; return buffered ? results.ToList() : results;
} }
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity) static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity)
{ {
identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }); identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) });
...@@ -594,177 +747,154 @@ class DontMap { } ...@@ -594,177 +747,154 @@ class DontMap { }
ownedReader = ownedCommand.ExecuteReader(); ownedReader = ownedCommand.ExecuteReader();
reader = ownedReader; reader = ownedReader;
} }
object deserializer; Func<IDataReader, object> deserializer = null;
object[] otherDeserializers; Func<IDataReader, object>[] otherDeserializers = null;
Action cacheDeserializers = () =>
{
var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth)}, splitOn, reader);
deserializer = cinfo.Deserializer = deserializers[0];
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
SetQueryCache(identity, cinfo);
};
if ((deserializer = cinfo.Deserializer) == null || (otherDeserializers = cinfo.OtherDeserializers) == null) if ((deserializer = cinfo.Deserializer) == null || (otherDeserializers = cinfo.OtherDeserializers) == null)
{ {
int current = 0; cacheDeserializers();
}
var splits = splitOn.Split(',').ToArray(); Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map);
var splitIndex = 0;
Func<Type,int> nextSplit = type => if (mapIt != null)
{
while (reader.Read())
{ {
var currentSplit = splits[splitIndex]; TReturn next;
if (splits.Length > splitIndex + 1) try
{ {
splitIndex++; next = mapIt(reader);
} }
catch (DataException)
bool skipFirst = false;
int startingPos = current + 1;
// if our current type has the split, skip the first time you see it.
if (type != typeof(Object))
{ {
var props = GetSettableProps(type); cacheDeserializers();
var fields = GetSettableFields(type); mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map);
next = mapIt(reader);
foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
{
if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
{
skipFirst = true;
startingPos = current;
break;
}
}
} }
yield return next;
int pos;
for (pos = startingPos; pos < reader.FieldCount; pos++)
{
// some people like ID some id ... assuming case insensitive splits for now
if (splitOn == "*")
{
break;
}
if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
{
if (skipFirst)
{
skipFirst = false;
}
else
{
break;
}
}
}
current = pos;
return pos;
};
var otherDeserializer = new List<object>();
int split = nextSplit(typeof(TFirst));
deserializer = cinfo.Deserializer = GetDeserializer<TFirst>(reader, 0, split, false);
if (typeof(TSecond) != typeof(DontMap))
{
var next = nextSplit(typeof(TSecond));
otherDeserializer.Add(GetDeserializer<TSecond>(reader, split, next - split, true));
split = next;
}
if (typeof(TThird) != typeof(DontMap))
{
var next = nextSplit(typeof(TThird));
otherDeserializer.Add(GetDeserializer<TThird>(reader, split, next - split, true));
split = next;
} }
if (typeof(TFourth) != typeof(DontMap)) }
}
finally
{
try
{
if (ownedReader != null)
{ {
var next = nextSplit(typeof(TFourth)); ownedReader.Dispose();
otherDeserializer.Add(GetDeserializer<TFourth>(reader, split, next - split, true));
split = next;
} }
if (typeof(TFifth) != typeof(DontMap)) }
finally
{
if (ownedCommand != null)
{ {
var next = nextSplit(typeof(TFifth)); ownedCommand.Dispose();
otherDeserializer.Add(GetDeserializer<TFifth>(reader, split, next - split, true));
} }
otherDeserializers = cinfo.OtherDeserializers = otherDeserializer.ToArray();
SetQueryCache(identity, cinfo);
} }
}
}
var rootDeserializer = (Func<IDataReader, TFirst>)deserializer; private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
var deserializer2 = (Func<IDataReader, TSecond>)otherDeserializers[0]; {
switch(otherDeserializers.Length)
{
case 1:
return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
case 2:
return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
case 3:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
#if !CSHARP30
case 4:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r));
#endif
default:
throw new NotSupportedException();
}
}
Func<IDataReader, TReturn> mapIt = null; private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
int current = 0;
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;
if (otherDeserializers.Length == 1) Func<Type, int> nextSplit = type =>
{
var currentSplit = splits[splitIndex];
if (splits.Length > splitIndex + 1)
{ {
mapIt = r => ((Func<TFirst, TSecond, TReturn>)map)(rootDeserializer(r), deserializer2(r)); splitIndex++;
} }
if (otherDeserializers.Length > 1) bool skipFirst = false;
int startingPos = current + 1;
// if our current type has the split, skip the first time you see it.
if (type != typeof(Object))
{ {
var deserializer3 = (Func<IDataReader, TThird>)otherDeserializers[1]; var props = GetSettableProps(type);
var fields = GetSettableFields(type);
if (otherDeserializers.Length == 2) foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
{ {
mapIt = r => ((Func<TFirst, TSecond, TThird, TReturn>)map)(rootDeserializer(r), deserializer2(r), deserializer3(r)); if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
}
if (otherDeserializers.Length > 2)
{
var deserializer4 = (Func<IDataReader, TFourth>)otherDeserializers[2];
if (otherDeserializers.Length == 3)
{ {
mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)(rootDeserializer(r), deserializer2(r), deserializer3(r), deserializer4(r)); skipFirst = true;
} startingPos = current;
break;
if (otherDeserializers.Length > 3)
{
#if CSHARP30
throw new NotSupportedException();
#else
var deserializer5 = (Func<IDataReader, TFifth>)otherDeserializers[3];
mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)(rootDeserializer(r), deserializer2(r), deserializer3(r), deserializer4(r), deserializer5(r));
#endif
} }
} }
} }
if (mapIt != null) int pos;
for (pos = startingPos; pos < reader.FieldCount; pos++)
{ {
bool clean = true; // some people like ID some id ... assuming case insensitive splits for now
try if (splitOn == "*")
{ {
while (reader.Read()) break;
{
clean = false;
TReturn next = mapIt(reader);
clean = true;
yield return next;
}
} }
finally if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
{ {
if (!clean) PurgeQueryCache(identity); if (skipFirst)
{
skipFirst = false;
}
else
{
break;
}
} }
} }
} current = pos;
finally return pos;
};
var deserializers = new List<Func<IDataReader, object>>();
int split = 0;
bool first = true;
foreach (var type in types)
{ {
try if (type != typeof(DontMap))
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{ {
if (ownedCommand != null) int next = nextSplit(type);
{ deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
ownedCommand.Dispose(); first = false;
} split = next;
} }
} }
return deserializers.ToArray();
} }
private static CacheInfo GetCacheInfo(Identity identity) private static CacheInfo GetCacheInfo(Identity identity)
...@@ -777,7 +907,7 @@ private static CacheInfo GetCacheInfo(Identity identity) ...@@ -777,7 +907,7 @@ private static CacheInfo GetCacheInfo(Identity identity)
{ {
if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType)) if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType))
{ {
info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd); }; info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd,identity); };
} }
else else
{ {
...@@ -789,23 +919,22 @@ private static CacheInfo GetCacheInfo(Identity identity) ...@@ -789,23 +919,22 @@ private static CacheInfo GetCacheInfo(Identity identity)
return info; return info;
} }
private static Func<IDataReader, T> GetDeserializer<T>(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{ {
Type type = typeof(T);
#if !CSHARP30 #if !CSHARP30
// dynamic is passed in as Object ... by c# design // dynamic is passed in as Object ... by c# design
if (type == typeof(object) if (type == typeof(object)
|| type == typeof(FastExpando)) || type == typeof(FastExpando))
{ {
return GetDynamicDeserializer<T>(reader, startBound, length, returnNullIfFirstMissing); return GetDynamicDeserializer(reader, startBound, length, returnNullIfFirstMissing);
} }
#endif #endif
if (type.IsClass && type != typeof(string) && type != typeof(byte[]) && type != typeof(System.Data.Linq.Binary)) if (!(typeMap.ContainsKey(type) || type.FullName == LinqBinary))
{ {
return GetClassDeserializer<T>(reader, startBound, length, returnNullIfFirstMissing); return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
} }
return GetStructDeserializer<T>(startBound); return GetStructDeserializer(type, startBound);
} }
#if !CSHARP30 #if !CSHARP30
...@@ -829,6 +958,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj ...@@ -829,6 +958,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj
return data.TryGetValue(binder.Name, out result); return data.TryGetValue(binder.Name, out result);
} }
public override IEnumerable<string> GetDynamicMemberNames()
{
return data.Keys;
}
#region IDictionary<string,object> Members #region IDictionary<string,object> Members
void IDictionary<string, object>.Add(string key, object value) void IDictionary<string, object>.Add(string key, object value)
...@@ -869,7 +1003,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj ...@@ -869,7 +1003,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj
} }
set set
{ {
throw new NotImplementedException(); if (!data.ContainsKey(key))
{
throw new NotImplementedException();
}
data[key] = value;
} }
} }
...@@ -934,7 +1072,7 @@ IEnumerator IEnumerable.GetEnumerator() ...@@ -934,7 +1072,7 @@ IEnumerator IEnumerable.GetEnumerator()
} }
private static Func<IDataReader, T> GetDynamicDeserializer<T>(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) private static Func<IDataReader, object> GetDynamicDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{ {
var fieldCount = reader.FieldCount; var fieldCount = reader.FieldCount;
if (length == -1) if (length == -1)
...@@ -958,14 +1096,19 @@ IEnumerator IEnumerable.GetEnumerator() ...@@ -958,14 +1096,19 @@ IEnumerator IEnumerable.GetEnumerator()
row[r.GetName(i)] = tmp; row[r.GetName(i)] = tmp;
if (returnNullIfFirstMissing && i == startBound && tmp == null) if (returnNullIfFirstMissing && i == startBound && tmp == null)
{ {
return default(T); return null;
} }
} }
//we know this is an object so it will not box //we know this is an object so it will not box
return (T)(object)FastExpando.Attach(row); return FastExpando.Attach(row);
}; };
} }
#endif #endif
/// <summary>
/// Internal use only
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)] [Obsolete("This method is for internal usage only", false)]
public static char ReadChar(object value) public static char ReadChar(object value)
...@@ -975,6 +1118,10 @@ public static char ReadChar(object value) ...@@ -975,6 +1118,10 @@ public static char ReadChar(object value)
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
return s[0]; return s[0];
} }
/// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)] [Obsolete("This method is for internal usage only", false)]
public static char? ReadNullableChar(object value) public static char? ReadNullableChar(object value)
...@@ -984,6 +1131,9 @@ public static char ReadChar(object value) ...@@ -984,6 +1131,9 @@ public static char ReadChar(object value)
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
return s[0]; return s[0];
} }
/// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", true)] [Obsolete("This method is for internal usage only", true)]
public static void PackListParameters(IDbCommand command, string namePrefix, object value) public static void PackListParameters(IDbCommand command, string namePrefix, object value)
...@@ -997,6 +1147,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj ...@@ -997,6 +1147,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
if (list != null) if (list != null)
{ {
bool isString = value is IEnumerable<string>; bool isString = value is IEnumerable<string>;
bool isDbString = value is IEnumerable<DbString>;
foreach (var item in list) foreach (var item in list)
{ {
count++; count++;
...@@ -1011,7 +1162,15 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj ...@@ -1011,7 +1162,15 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
listParam.Size = -1; listParam.Size = -1;
} }
} }
command.Parameters.Add(listParam); if (isDbString && item as DbString != null)
{
var str = item as DbString;
str.AddParameter(command, listParam.ParameterName);
}
else
{
command.Parameters.Add(listParam);
}
} }
if (count == 0) if (count == 0)
...@@ -1040,11 +1199,13 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn ...@@ -1040,11 +1199,13 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline)); return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline));
} }
private static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity) /// <summary>
/// Internal use only
/// </summary>
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity)
{ {
Type type = identity.parametersType; Type type = identity.parametersType;
bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text; bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
var il = dm.GetILGenerator(); var il = dm.GetILGenerator();
...@@ -1164,9 +1325,9 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn ...@@ -1164,9 +1325,9 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
il.MarkLabel(lenDone); il.MarkLabel(lenDone);
il.Emit(OpCodes.Stloc_1); // [string] il.Emit(OpCodes.Stloc_1); // [string]
} }
if (prop.PropertyType == typeof(System.Data.Linq.Binary)) if (prop.PropertyType.FullName == LinqBinary)
{ {
il.EmitCall(OpCodes.Callvirt, typeof(System.Data.Linq.Binary).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null); il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null);
} }
if (allDone != null) il.MarkLabel(allDone.Value); if (allDone != null) il.MarkLabel(allDone.Value);
// relative stack [boxed value or DBNull] // relative stack [boxed value or DBNull]
...@@ -1215,39 +1376,35 @@ private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transac ...@@ -1215,39 +1376,35 @@ private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transac
} }
private static int ExecuteCommand(IDbConnection cnn, IDbTransaction tranaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) private static int ExecuteCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
{ {
using (var cmd = SetupCommand(cnn, tranaction, sql, paramReader, obj, commandTimeout, commandType)) using (var cmd = SetupCommand(cnn, transaction, sql, paramReader, obj, commandTimeout, commandType))
{ {
return cmd.ExecuteNonQuery(); return cmd.ExecuteNonQuery();
} }
} }
private static Func<IDataReader, T> GetStructDeserializer<T>(int index) private static Func<IDataReader, object> GetStructDeserializer(Type type, int index)
{ {
// no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
#pragma warning disable 618 #pragma warning disable 618
if (typeof(T) == typeof(char)) if (type == typeof(char))
{ // this *does* need special handling, though { // this *does* need special handling, though
return (Func<IDataReader, T>)(object)new Func<IDataReader, char>(r => SqlMapper.ReadChar(r.GetValue(index))); return r => SqlMapper.ReadChar(r.GetValue(index));
} }
if (typeof(T) == typeof(char?)) if (type == typeof(char?))
{ {
return (Func<IDataReader, T>)(object)new Func<IDataReader, char?>(r => SqlMapper.ReadNullableChar(r.GetValue(index))); return r => SqlMapper.ReadNullableChar(r.GetValue(index));
} }
if (typeof(T) == typeof(System.Data.Linq.Binary)) if (type.FullName == LinqBinary)
{ {
return (Func<IDataReader, T>)(object)new Func<IDataReader, System.Data.Linq.Binary>(r => new System.Data.Linq.Binary((byte[])r.GetValue(index))); return r => Activator.CreateInstance(type, r.GetValue(index));
} }
#pragma warning restore 618 #pragma warning restore 618
return r => return r =>
{ {
var val = r.GetValue(index); var val = r.GetValue(index);
if (val == DBNull.Value) return val is DBNull ? null : val;
{
val = null;
}
return (T)val;
}; };
} }
...@@ -1271,7 +1428,9 @@ static List<PropInfo> GetSettableProps(Type t) ...@@ -1271,7 +1428,9 @@ static List<PropInfo> GetSettableProps(Type t)
.Select(p => new PropInfo .Select(p => new PropInfo
{ {
Name = p.Name, Name = p.Name,
Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true), Setter = p.DeclaringType == t ?
p.GetSetMethod(true) :
p.DeclaringType.GetProperty(p.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true),
Type = p.PropertyType Type = p.PropertyType
}) })
.Where(info => info.Setter != null) .Where(info => info.Setter != null)
...@@ -1283,24 +1442,33 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1283,24 +1442,33 @@ static List<FieldInfo> GetSettableFields(Type t)
return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
} }
public static Func<IDataReader, T> GetClassDeserializer<T>( /// <summary>
/// Internal use only
/// </summary>
/// <param name="type"></param>
/// <param name="reader"></param>
/// <param name="startBound"></param>
/// <param name="length"></param>
/// <param name="returnNullIfFirstMissing"></param>
/// <returns></returns>
public static Func<IDataReader, object> GetTypeDeserializer(
#if CSHARP30 #if CSHARP30
IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#else #else
IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
#endif #endif
) )
{ {
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(T), new[] { typeof(IDataReader) }, true); var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator(); var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int)); il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(T)); il.DeclareLocal(type);
bool haveEnumLocal = false; bool haveEnumLocal = false;
il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0); il.Emit(OpCodes.Stloc_0);
var properties = GetSettableProps(typeof(T)); var properties = GetSettableProps(type);
var fields = GetSettableFields(typeof(T)); var fields = GetSettableFields(type);
if (length == -1) if (length == -1)
{ {
length = reader.FieldCount - startBound; length = reader.FieldCount - startBound;
...@@ -1329,9 +1497,27 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1329,9 +1497,27 @@ static List<FieldInfo> GetSettableFields(Type t)
int index = startBound; int index = startBound;
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);
il.Emit(OpCodes.Initobj, type);
}
else
{
il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null));
il.Emit(OpCodes.Stloc_1);
}
il.BeginExceptionBlock(); il.BeginExceptionBlock();
// stack is empty if(type.IsValueType)
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target] {
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
} else
{
il.Emit(OpCodes.Ldloc_1);// [target]
}
// stack is now [target]
bool first = true; bool first = true;
var allDone = il.DefineLabel(); var allDone = il.DefineLabel();
foreach (var item in setters) foreach (var item in setters)
...@@ -1348,7 +1534,6 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1348,7 +1534,6 @@ static List<FieldInfo> GetSettableFields(Type t)
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType; Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType;
if (memberType == typeof(char) || memberType == typeof(char?)) if (memberType == typeof(char) || memberType == typeof(char?))
...@@ -1410,10 +1595,10 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1410,10 +1595,10 @@ static List<FieldInfo> GetSettableFields(Type t)
il.MarkLabel(isNotString); il.MarkLabel(isNotString);
} }
if (memberType == typeof(System.Data.Linq.Binary)) if (memberType.FullName == LinqBinary)
{ {
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
} }
else else
{ {
...@@ -1426,7 +1611,14 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1426,7 +1611,14 @@ static List<FieldInfo> GetSettableFields(Type t)
} }
if (item.Property != null) if (item.Property != null)
{ {
il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target] if (type.IsValueType)
{
il.Emit(OpCodes.Call, item.Property.Setter); // stack is now [target]
}
else
{
il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
}
} }
else else
{ {
...@@ -1453,21 +1645,37 @@ static List<FieldInfo> GetSettableFields(Type t) ...@@ -1453,21 +1645,37 @@ static List<FieldInfo> GetSettableFields(Type t)
first = false; first = false;
index += 1; index += 1;
} }
il.Emit(OpCodes.Stloc_1); // stack is empty if (type.IsValueType)
{
il.Emit(OpCodes.Pop);
}
else
{
il.Emit(OpCodes.Stloc_1); // stack is empty
}
il.MarkLabel(allDone); il.MarkLabel(allDone);
il.BeginCatchBlock(typeof(Exception)); // stack is Exception il.BeginCatchBlock(typeof(Exception)); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc_1); // to make it verifiable
il.EndExceptionBlock(); il.EndExceptionBlock();
il.Emit(OpCodes.Ldloc_1); // stack is empty il.Emit(OpCodes.Ldloc_1); // stack is [rval]
if(type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
return (Func<IDataReader, T>)dm.CreateDelegate(typeof(Func<IDataReader, T>)); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader,object>));
} }
/// <summary>
/// Throws a data exception, only used internally
/// </summary>
/// <param name="ex"></param>
/// <param name="index"></param>
/// <param name="reader"></param>
public static void ThrowDataException(Exception ex, int index, IDataReader reader) public static void ThrowDataException(Exception ex, int index, IDataReader reader)
{ {
string name = "(n/a)", value = "(n/a)"; string name = "(n/a)", value = "(n/a)";
...@@ -1513,6 +1721,9 @@ private static void EmitInt32(ILGenerator il, int value) ...@@ -1513,6 +1721,9 @@ private static void EmitInt32(ILGenerator il, int value)
} }
} }
/// <summary>
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query
/// </summary>
public class GridReader : IDisposable public class GridReader : IDisposable
{ {
private IDataReader reader; private IDataReader reader;
...@@ -1534,14 +1745,21 @@ public IEnumerable<T> Read<T>() ...@@ -1534,14 +1745,21 @@ public IEnumerable<T> Read<T>()
if (consumed) throw new InvalidOperationException("Each grid can only be iterated once"); if (consumed) throw new InvalidOperationException("Each grid can only be iterated once");
var typedIdentity = identity.ForGrid(typeof(T), gridIndex); var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity); CacheInfo cache = GetCacheInfo(typedIdentity);
var deserializer = (Func<IDataReader, T>)cache.Deserializer; var deserializer = cache.Deserializer;
if (deserializer == null)
Func<Func<IDataReader, object>> deserializerGenerator = () =>
{ {
deserializer = GetDeserializer<T>(reader, 0, -1, false); deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
return deserializer;
};
if (deserializer == null)
{
deserializer = deserializerGenerator();
} }
consumed = true; consumed = true;
return ReadDeferred(gridIndex, deserializer, typedIdentity); return ReadDeferred<T>(gridIndex, deserializer, typedIdentity, deserializerGenerator);
} }
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(object func, string splitOn) private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(object func, string splitOn)
...@@ -1567,6 +1785,15 @@ public IEnumerable<T> Read<T>() ...@@ -1567,6 +1785,15 @@ public IEnumerable<T> Read<T>()
} }
} }
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="func"></param>
/// <param name="splitOn"></param>
/// <returns></returns>
#if CSHARP30 #if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn) public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
#else #else
...@@ -1576,6 +1803,16 @@ public IEnumerable<T> Read<T>() ...@@ -1576,6 +1803,16 @@ public IEnumerable<T> Read<T>()
return MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(func, splitOn); return MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
} }
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="func"></param>
/// <param name="splitOn"></param>
/// <returns></returns>
#if CSHARP30 #if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn) public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
#else #else
...@@ -1585,6 +1822,17 @@ public IEnumerable<T> Read<T>() ...@@ -1585,6 +1822,17 @@ public IEnumerable<T> Read<T>()
return MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(func, splitOn); return MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(func, splitOn);
} }
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="func"></param>
/// <param name="splitOn"></param>
/// <returns></returns>
#if CSHARP30 #if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn) public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else #else
...@@ -1595,6 +1843,18 @@ public IEnumerable<T> Read<T>() ...@@ -1595,6 +1843,18 @@ public IEnumerable<T> Read<T>()
} }
#if !CSHARP30 #if !CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="func"></param>
/// <param name="splitOn"></param>
/// <returns></returns>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id") public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id")
{ {
...@@ -1602,26 +1862,27 @@ public IEnumerable<T> Read<T>() ...@@ -1602,26 +1862,27 @@ public IEnumerable<T> Read<T>()
} }
#endif #endif
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, T> deserializer, Identity typedIdentity) private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity, Func<Func<IDataReader, object>> deserializerGenerator)
{ {
bool clean = true;
try try
{ {
while (index == gridIndex && reader.Read()) while (index == gridIndex && reader.Read())
{ {
clean = false; object next;
T next = deserializer(reader); try
clean = true; {
yield return next; next = deserializer(reader);
}
catch (DataException)
{
deserializer = deserializerGenerator();
next = deserializer(reader);
}
yield return (T)next;
} }
} }
finally // finally so that First etc progresses things even when multiple rows finally // finally so that First etc progresses things even when multiple rows
{ {
if (!clean)
{
PurgeQueryCache(typedIdentity);
}
if (index == gridIndex) if (index == gridIndex)
{ {
NextResult(); NextResult();
...@@ -1643,6 +1904,9 @@ private void NextResult() ...@@ -1643,6 +1904,9 @@ private void NextResult()
} }
} }
/// <summary>
/// Dispose the grid, closing and disposing both the underlying reader and command.
/// </summary>
public void Dispose() public void Dispose()
{ {
if (reader != null) if (reader != null)
...@@ -1658,9 +1922,16 @@ public void Dispose() ...@@ -1658,9 +1922,16 @@ public void Dispose()
} }
} }
} }
/// <summary>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary>
public class DynamicParameters : SqlMapper.IDynamicParameters public class DynamicParameters : SqlMapper.IDynamicParameters
{ {
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>(); Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
List<object> templates;
class ParamInfo class ParamInfo
{ {
...@@ -1672,28 +1943,75 @@ class ParamInfo ...@@ -1672,28 +1943,75 @@ class ParamInfo
public IDbDataParameter AttachedParam { get; set; } public IDbDataParameter AttachedParam { get; set; }
} }
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public DynamicParameters() { } public DynamicParameters() { }
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type of a DynamicParameters bag</param>
public DynamicParameters(object template) public DynamicParameters(object template)
{ {
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
if (template != null) if (template != null)
{ {
foreach (PropertyInfo prop in template.GetType().GetProperties(bindingFlags)) AddDynamicParams(template);
}
}
/// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(
#if CSHARP30
object param
#else
dynamic param
#endif
)
{
object obj = param as object;
if (obj != null)
{
var subDynamic = obj as DynamicParameters;
if (subDynamic == null)
{ {
if (!prop.CanRead) continue; templates = templates ?? new List<object>();
var idx = prop.GetIndexParameters(); templates.Add(obj);
if (idx != null && idx.Length != 0) continue;
Add(prop.Name, prop.GetValue(template, null), null, ParameterDirection.Input, null);
} }
foreach (FieldInfo field in template.GetType().GetFields(bindingFlags)) else
{ {
Add(field.Name, field.GetValue(template), null, ParameterDirection.Input, null); if (subDynamic.parameters != null)
} {
foreach (var kvp in subDynamic.parameters)
{
parameters.Add(kvp.Key, kvp.Value);
}
}
if (subDynamic.templates != null)
{
foreach (var t in subDynamic.templates)
{
templates.Add(t);
}
}
}
} }
} }
/// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="dbType"></param>
/// <param name="direction"></param>
/// <param name="size"></param>
public void Add( public void Add(
#if CSHARP30 #if CSHARP30
string name, object value, DbType? dbType, ParameterDirection? direction, int? size string name, object value, DbType? dbType, ParameterDirection? direction, int? size
...@@ -1720,13 +2038,42 @@ static string Clean(string name) ...@@ -1720,13 +2038,42 @@ static string Clean(string name)
return name; return name;
} }
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command) void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{ {
if (templates != null)
{
foreach (var template in templates)
{
var newIdent = identity.ForDynamicParameters(template.GetType());
Action<IDbCommand, object> appender;
lock (paramReaderCache)
{
if (!paramReaderCache.TryGetValue(newIdent, out appender))
{
appender = SqlMapper.CreateParamInfoGenerator(newIdent);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
}
foreach (var param in parameters.Values) foreach (var param in parameters.Values)
{ {
var p = command.CreateParameter(); string name = Clean(param.Name);
bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if(add)
{
p = command.CreateParameter();
p.ParameterName = name;
} else
{
p = (IDbDataParameter)command.Parameters[name];
}
var val = param.Value; var val = param.Value;
p.ParameterName = param.Name;
p.Value = val ?? DBNull.Value; p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection; p.Direction = param.ParameterDirection;
var s = val as string; var s = val as string;
...@@ -1745,11 +2092,32 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command) ...@@ -1745,11 +2092,32 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command)
{ {
p.DbType = param.DbType.Value; p.DbType = param.DbType.Value;
} }
command.Parameters.Add(p); if (add)
{
command.Parameters.Add(p);
}
param.AttachedParam = p; param.AttachedParam = p;
} }
} }
/// <summary>
/// All the names of the param in the bag, use Get to yank them out
/// </summary>
public IEnumerable<string> ParameterNames
{
get
{
return parameters.Select(p => p.Key);
}
}
/// <summary>
/// Get the value of a parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
public T Get<T>(string name) public T Get<T>(string name)
{ {
var val = parameters[Clean(name)].AttachedParam.Value; var val = parameters[Clean(name)].AttachedParam.Value;
...@@ -1764,13 +2132,37 @@ public T Get<T>(string name) ...@@ -1764,13 +2132,37 @@ public T Get<T>(string name)
return (T)val; return (T)val;
} }
} }
/// <summary>
/// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar
/// </summary>
public sealed class DbString public sealed class DbString
{ {
/// <summary>
/// Create a new DbString
/// </summary>
public DbString() { Length = -1; } public DbString() { Length = -1; }
/// <summary>
/// Ansi vs Unicode
/// </summary>
public bool IsAnsi { get; set; } public bool IsAnsi { get; set; }
/// <summary>
/// Fixed length
/// </summary>
public bool IsFixedLength { get; set; } public bool IsFixedLength { get; set; }
/// <summary>
/// Length of the string -1 for max
/// </summary>
public int Length { get; set; } public int Length { get; set; }
/// <summary>
/// The value of the string
/// </summary>
public string Value { get; set; } public string Value { get; set; }
/// <summary>
/// Add the parameter to the command... internal use only
/// </summary>
/// <param name="command"></param>
/// <param name="name"></param>
public void AddParameter(IDbCommand command, string name) public void AddParameter(IDbCommand command, string name)
{ {
if (IsFixedLength && Length == -1) if (IsFixedLength && Length == -1)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata schemaVersion="2"> <metadata schemaVersion="2">
<id>Dapper</id> <id>Dapper</id>
<version>1.6</version> <version>1.8</version>
<title>Dapper dot net</title> <title>Dapper dot net</title>
<authors>Sam Saffron,Marc Gravell</authors> <authors>Sam Saffron,Marc Gravell</authors>
<owners>Sam Saffron,Marc Gravell</owners> <owners>Sam Saffron,Marc Gravell</owners>
...@@ -11,15 +11,24 @@ ...@@ -11,15 +11,24 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A high performance Micro-ORM supporting Sql Server, MySQL, Sqlite, SqlCE, Firebird etc..</description> <description>A high performance Micro-ORM supporting Sql Server, MySQL, Sqlite, SqlCE, Firebird etc..</description>
<summary>A high performance Micro-ORM</summary> <summary>A high performance Micro-ORM</summary>
<tags>orm,sql,micro-orm</tags> <tags>orm sql micro-orm</tags>
<frameworkAssemblies> <frameworkAssemblies>
<frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System.Core"/>
<frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System"/>
<frameworkAssembly assemblyName="System.Data" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System.Data"/>
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
</frameworkAssemblies> </frameworkAssemblies>
<releaseNotes>
* 1.8 - Started release notes
* Important: Dapper is now shipping as a DLL which will work on .net 3.5 or .net 4.0,
* This improves the debugging experience as you no longer break into dapper when SQL fails.
* Added: ParameterNames on DynamicParameters
</releaseNotes>
</metadata> </metadata>
<files> <files>
<file src="SqlMapper.cs" target="content\Dapper\SqlMapper.cs" /> <file src="bin\Release\Dapper.dll" target="lib\net40" />
<file src="bin\Release\Dapper.pdb" target="lib\net40" />
<file src="..\Dapper NET35\bin\Release\Dapper.dll" target="lib\net35" />
<file src="..\Dapper NET35\bin\Release\Dapper.pdb" target="lib\net35" />
</files> </files>
</package> </package>
\ No newline at end of file
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
<LastGenOutput>DataClasses.designer.cs</LastGenOutput> <LastGenOutput>DataClasses.designer.cs</LastGenOutput>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Data.Linq; using System.Data.Linq;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Diagnostics; using System.Diagnostics;
...@@ -9,6 +10,8 @@ ...@@ -9,6 +10,8 @@
using Massive; using Massive;
using NHibernate.Criterion; using NHibernate.Criterion;
using NHibernate.Linq; using NHibernate.Linq;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.SqlServer;
using SqlMapper.Linq2Sql; using SqlMapper.Linq2Sql;
using SqlMapper.NHibernate; using SqlMapper.NHibernate;
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
...@@ -191,6 +194,11 @@ public void Run(int iterations) ...@@ -191,6 +194,11 @@ public void Run(int iterations)
// Soma // Soma
var somadb = new Soma.Core.Db(new SomaConfig()); var somadb = new Soma.Core.Db(new SomaConfig());
tests.Add(id => somadb.Find<Post>(id), "Soma"); tests.Add(id => somadb.Find<Post>(id), "Soma");
//ServiceStack's OrmLite:
OrmLiteConfig.DialectProvider = SqlServerOrmLiteDialectProvider.Instance; //Using SQL Server
IDbCommand ormLiteCmd = Program.GetOpenConnection().CreateCommand();
tests.Add(id => ormLiteCmd.QueryById<Post>(id), "OrmLite QueryById");
// HAND CODED // HAND CODED
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
namespace SqlMapper namespace SqlMapper
{ {
[ServiceStack.DataAnnotations.Alias("Posts")]
[Soma.Core.Table(Name = "Posts")] [Soma.Core.Table(Name = "Posts")]
class Post class Post
{ {
......
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="tempdbEntities1" Identifier="SqlMapper.EntityFramework.tempdbEntities1" ProviderType="Microsoft.VisualStudio.DataDesign.DataSourceProviders.EntityDataModel.EdmDataSourceProvider" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>SqlMapper.EntityFramework.tempdbEntities1, EntityFramework.Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
\ No newline at end of file
...@@ -7,16 +7,93 @@ ...@@ -7,16 +7,93 @@
using System.IO; using System.IO;
using System.Data; using System.Data;
using System.Collections; using System.Collections;
using System.Reflection;
namespace SqlMapper namespace SqlMapper
{ {
class Tests class Tests
{ {
SqlConnection connection = Program.GetOpenConnection(); SqlConnection connection = Program.GetOpenConnection();
public class AbstractInheritance
{
public abstract class Order
{
internal int Internal { get; set; }
protected int Protected { get; set; }
public int Public { get; set; }
public int ProtectedVal { get { return Protected; } }
}
public class ConcreteOrder : Order
{
public int Concrete { get; set; }
}
}
// http://stackoverflow.com/q/8593871
public void TestAbstractInheritance()
{
var order = connection.Query<AbstractInheritance.ConcreteOrder>("select 1 Internal,2 Protected,3 [Public],4 Concrete").First();
order.Internal.IsEqualTo(1);
order.ProtectedVal.IsEqualTo(2);
order.Public.IsEqualTo(3);
order.Concrete.IsEqualTo(4);
}
public void TestListOfAnsiStrings()
{
var results = connection.Query<string>("select * from (select 'a' str union select 'b' union select 'c') X where str in @strings",
new { strings = new[] { new DbString { IsAnsi = true, Value = "a" }, new DbString { IsAnsi = true, Value = "b" } } }).ToList();
results[0].IsEqualTo("a");
results[1].IsEqualTo("b");
}
public void TestNullableGuidSupport()
{
var guid = connection.Query<Guid?>("select null").First();
guid.IsNull();
guid = Guid.NewGuid();
var guid2 = connection.Query<Guid?>("select @guid", new { guid }).First();
guid.IsEqualTo(guid2);
}
public void TestNonNullableGuidSupport()
{
var guid = Guid.NewGuid();
var guid2 = connection.Query<Guid?>("select @guid", new { guid }).First();
Assert.IsTrue(guid == guid2);
}
struct Car
{
public enum TrapEnum : int
{
A = 1,
B = 2
}
public string Name;
public int Age { get; set; }
public TrapEnum Trap { get; set; }
}
public void TestStructs()
{
var car = connection.Query<Car>("select 'Ford' Name, 21 Age, 2 Trap").First();
car.Age.IsEqualTo(21);
car.Name.IsEqualTo("Ford");
((int)car.Trap).IsEqualTo(2);
}
public void SelectListInt() public void SelectListInt()
{ {
connection.Query<int>("select 1 union all select 2 union all select 3") connection.Query<int>("select 1 union all select 2 union all select 3")
...@@ -24,7 +101,7 @@ public void SelectListInt() ...@@ -24,7 +101,7 @@ public void SelectListInt()
} }
public void SelectBinary() public void SelectBinary()
{ {
connection.Query<byte[]>("select cast(1 as varbinary(4))").First().SequenceEqual(new byte[] {1}); connection.Query<byte[]>("select cast(1 as varbinary(4))").First().SequenceEqual(new byte[] { 1 });
} }
public void PassInIntArray() public void PassInIntArray()
{ {
...@@ -38,6 +115,40 @@ public void PassInEmptyIntArray() ...@@ -38,6 +115,40 @@ public void PassInEmptyIntArray()
.IsSequenceEqualTo(new int[0]); .IsSequenceEqualTo(new int[0]);
} }
public void TestSchemaChanged()
{
connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')");
var d = connection.Query<Dog>("select * from #dog").Single();
d.Name.IsEqualTo("Alf");
d.Age.IsEqualTo(1);
connection.Execute("alter table #dog drop column Name");
d = connection.Query<Dog>("select * from #dog").Single();
d.Name.IsNull();
d.Age.IsEqualTo(1);
connection.Execute("drop table #dog");
}
public void TestSchemaChangedMultiMap()
{
connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')");
var tuple = connection.Query<Dog, Dog, Tuple<Dog, Dog>>("select * from #dog d1 join #dog d2 on 1=1", (d1, d2) => Tuple.Create(d1, d2), splitOn: "Age").Single();
tuple.Item1.Name.IsEqualTo("Alf");
tuple.Item1.Age.IsEqualTo(1);
tuple.Item2.Name.IsEqualTo("Alf");
tuple.Item2.Age.IsEqualTo(1);
connection.Execute("alter table #dog drop column Name");
tuple = connection.Query<Dog, Dog, Tuple<Dog, Dog>>("select * from #dog d1 join #dog d2 on 1=1", (d1, d2) => Tuple.Create(d1, d2), splitOn: "Age").Single();
tuple.Item1.Name.IsNull();
tuple.Item1.Age.IsEqualTo(1);
tuple.Item2.Name.IsNull();
tuple.Item2.Age.IsEqualTo(1);
connection.Execute("drop table #dog");
}
public void TestReadMultipleIntegersWithSplitOnAny() public void TestReadMultipleIntegersWithSplitOnAny()
{ {
connection.Query<int, int, int, Tuple<int, int, int>>( connection.Query<int, int, int, Tuple<int, int, int>>(
...@@ -185,7 +296,7 @@ public void TestExecuteMultipleCommand() ...@@ -185,7 +296,7 @@ public void TestExecuteMultipleCommand()
class Student class Student
{ {
public string Name {get; set;} public string Name { get; set; }
public int Age { get; set; } public int Age { get; set; }
} }
...@@ -224,7 +335,7 @@ class TestObj ...@@ -224,7 +335,7 @@ class TestObj
internal int Internal { set { _internal = value; } } internal int Internal { set { _internal = value; } }
public int _priv; public int _priv;
internal int Priv { set { _priv = value; } } private int Priv { set { _priv = value; } }
} }
public void TestSetInternal() public void TestSetInternal()
...@@ -287,6 +398,26 @@ public void TestEnumerationDynamic() ...@@ -287,6 +398,26 @@ public void TestEnumerationDynamic()
gotException.IsTrue(); gotException.IsTrue();
} }
public void TestNakedBigInt()
{
long foo = 12345;
var result = connection.Query<long>("select @foo", new { foo }).Single();
foo.IsEqualTo(result);
}
public void TestBigIntMember()
{
long foo = 12345;
var result = connection.Query<WithBigInt>(@"
declare @bar table(Value bigint)
insert @bar values (@foo)
select * from @bar", new { foo }).Single();
result.Value.IsEqualTo(foo);
}
class WithBigInt
{
public long Value { get; set; }
}
class User class User
{ {
...@@ -374,7 +505,7 @@ public void TestMultiMapGridReader() ...@@ -374,7 +505,7 @@ public void TestMultiMapGridReader()
data[2].Owner.IsNull(); data[2].Owner.IsNull();
} }
connection.Execute("drop table #Users drop table #Posts"); connection.Execute("drop table #Users drop table #Posts");
} }
...@@ -757,7 +888,7 @@ public IntDynamicParam(IEnumerable<int> numbers) ...@@ -757,7 +888,7 @@ public IntDynamicParam(IEnumerable<int> numbers)
this.numbers = numbers; this.numbers = numbers;
} }
public void AddParameters(IDbCommand command) public void AddParameters(IDbCommand command, Dapper.SqlMapper.Identity identity)
{ {
var sqlCommand = (SqlCommand)command; var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.CommandType = CommandType.StoredProcedure;
...@@ -862,10 +993,12 @@ class WithBizarreData ...@@ -862,10 +993,12 @@ class WithBizarreData
public void TestUnexpectedDataMessage() public void TestUnexpectedDataMessage()
{ {
string msg = null; string msg = null;
try { try
{
connection.Query<int>("select count(1) where 1 = @Foo", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).First(); connection.Query<int>("select count(1) where 1 = @Foo", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).First();
} catch(Exception ex) }
catch (Exception ex)
{ {
msg = ex.Message; msg = ex.Message;
} }
...@@ -923,7 +1056,7 @@ public void TestInvalidSplitCausesNiceError() ...@@ -923,7 +1056,7 @@ public void TestInvalidSplitCausesNiceError()
connection.Query<User, User, User>("select 1 A, 2 B, 3 C", (x, y) => x); connection.Query<User, User, User>("select 1 A, 2 B, 3 C", (x, y) => x);
} }
catch (ArgumentException) catch (ArgumentException)
{ {
// expecting an app exception due to multi mapping being bodged // expecting an app exception due to multi mapping being bodged
} }
...@@ -989,19 +1122,19 @@ public void TestDynamicParamNullSupport() ...@@ -989,19 +1122,19 @@ public void TestDynamicParamNullSupport()
var p = new DynamicParameters(); var p = new DynamicParameters();
p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output); p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);
connection.Execute("select @b = null",p); connection.Execute("select @b = null", p);
p.Get<int?>("@b").IsNull(); p.Get<int?>("@b").IsNull();
} }
class Foo1 class Foo1
{ {
public int Id; public int Id;
public int BarId { get; set; } public int BarId { get; set; }
} }
class Bar1 class Bar1
{ {
public int BarId; public int BarId;
public string Name { get; set; } public string Name { get; set; }
} }
public void TestMultiMapperIsNotConfusedWithUnorderedCols() public void TestMultiMapperIsNotConfusedWithUnorderedCols()
{ {
...@@ -1051,5 +1184,156 @@ public void TestWithNonPublicConstructor() ...@@ -1051,5 +1184,156 @@ public void TestWithNonPublicConstructor()
var output = connection.Query<WithPrivateConstructor>("select 1 as Foo").First(); var output = connection.Query<WithPrivateConstructor>("select 1 as Foo").First();
output.Foo.IsEqualTo(1); output.Foo.IsEqualTo(1);
} }
public void TestAppendingAnonClasses()
{
DynamicParameters p = new DynamicParameters();
p.AddDynamicParams(new { A = 1, B = 2 });
p.AddDynamicParams(new { C = 3, D = 4 });
var result = connection.Query("select @A a,@B b,@C c,@D d", p).Single();
((int)result.a).IsEqualTo(1);
((int)result.b).IsEqualTo(2);
((int)result.c).IsEqualTo(3);
((int)result.d).IsEqualTo(4);
}
public void TestAppendingAList()
{
DynamicParameters p = new DynamicParameters();
var list = new int[] { 1, 2, 3 };
p.AddDynamicParams(new { list });
var result = connection.Query<int>("select * from (select 1 A union all select 2 union all select 3) X where A in @list", p).ToList();
result[0].IsEqualTo(1);
result[1].IsEqualTo(2);
result[2].IsEqualTo(3);
}
public void TestUniqueIdentifier()
{
var guid = Guid.NewGuid();
var result = connection.Query<Guid>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
result.IsEqualTo(guid);
}
public void TestNullableUniqueIdentifierNonNull()
{
Guid? guid = Guid.NewGuid();
var result = connection.Query<Guid?>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
result.IsEqualTo(guid);
}
public void TestNullableUniqueIdentifierNull()
{
Guid? guid = null;
var result = connection.Query<Guid?>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
result.IsEqualTo(guid);
}
public void TestFailInASaneWayWithWrongStructColumnTypes()
{
try
{
connection.Query<CanHazInt>("select cast(1 as bigint) Value").Single();
throw new Exception("Should not have got here");
} catch(DataException ex)
{
ex.Message.IsEqualTo("Error parsing column 0 (Value=1 - Int64)");
}
}
public void TestProcWithOutParameter()
{
connection.Execute(
@"CREATE PROCEDURE #TestProcWithOutParameter
@ID int output,
@Foo varchar(100),
@Bar int
AS
SET @ID = @Bar + LEN(@Foo)");
var obj = new
{
ID = 0,
Foo = "abc",
Bar = 4
};
var args = new DynamicParameters(obj);
args.Add("ID", 0, direction: ParameterDirection.Output);
connection.Execute("#TestProcWithOutParameter", args, commandType: CommandType.StoredProcedure);
args.Get<int>("ID").IsEqualTo(7);
}
public void TestProcWithOutAndReturnParameter()
{
connection.Execute(
@"CREATE PROCEDURE #TestProcWithOutAndReturnParameter
@ID int output,
@Foo varchar(100),
@Bar int
AS
SET @ID = @Bar + LEN(@Foo)
RETURN 42");
var obj = new
{
ID = 0,
Foo = "abc",
Bar = 4
};
var args = new DynamicParameters(obj);
args.Add("ID", 0, direction: ParameterDirection.Output);
args.Add("result", 0, direction: ParameterDirection.ReturnValue);
connection.Execute("#TestProcWithOutAndReturnParameter", args, commandType: CommandType.StoredProcedure);
args.Get<int>("ID").IsEqualTo(7);
args.Get<int>("result").IsEqualTo(42);
}
struct CanHazInt
{
public int Value { get; set; }
}
public void TestInt16Usage()
{
connection.Query<short>("select cast(42 as smallint)").Single().IsEqualTo((short)42);
connection.Query<short?>("select cast(42 as smallint)").Single().IsEqualTo((short?)42);
connection.Query<short?>("select cast(null as smallint)").Single().IsEqualTo((short?)null);
// hmmm.... these don't work currently... adding TODO
//connection.Query<ShortEnum>("select cast(42 as smallint)").Single().IsEqualTo((ShortEnum)42);
//connection.Query<ShortEnum?>("select cast(42 as smallint)").Single().IsEqualTo((ShortEnum?)42);
//connection.Query<ShortEnum?>("select cast(null as smallint)").Single().IsEqualTo((ShortEnum?)null);
var row =
connection.Query<WithInt16Values>(
"select cast(1 as smallint) as NonNullableInt16, cast(2 as smallint) as NullableInt16, cast(3 as smallint) as NonNullableInt16Enum, cast(4 as smallint) as NullableInt16Enum")
.Single();
row.NonNullableInt16.IsEqualTo((short)1);
row.NullableInt16.IsEqualTo((short)2);
row.NonNullableInt16Enum.IsEqualTo(ShortEnum.Three);
row.NullableInt16Enum.IsEqualTo(ShortEnum.Four);
row =
connection.Query<WithInt16Values>(
"select cast(5 as smallint) as NonNullableInt16, cast(null as smallint) as NullableInt16, cast(6 as smallint) as NonNullableInt16Enum, cast(null as smallint) as NullableInt16Enum")
.Single();
row.NonNullableInt16.IsEqualTo((short)5);
row.NullableInt16.IsEqualTo((short?)null);
row.NonNullableInt16Enum.IsEqualTo(ShortEnum.Six);
row.NullableInt16Enum.IsEqualTo((ShortEnum?)null);
}
public class WithInt16Values
{
public short NonNullableInt16 { get; set; }
public short? NullableInt16 { get; set; }
public ShortEnum NonNullableInt16Enum { get; set; }
public ShortEnum? NullableInt16Enum { get; set; }
}
public enum ShortEnum : short
{
Zero = 0, One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment