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 @@
bin/
obj/
/*.user
_Resharper*
\ No newline at end of file
_Resharper*
.hgtags
NuGet.exe
*.user
*.nupkg
.docstats
......@@ -8,8 +8,8 @@
<ProjectGuid>{B26305D8-3A89-4D68-A981-9BBF378B81FA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper_NET35</RootNamespace>
<AssemblyName>Dapper NET35</AssemblyName>
<RootNamespace>Dapper</RootNamespace>
<AssemblyName>Dapper</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
......@@ -36,7 +36,6 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Data.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Dapper\Properties\AssemblyInfo.cs">
......
......@@ -65,6 +65,10 @@
<Project>{C2FC4DF5-C8D1-4EA8-8E0C-85A3793EB0BB}</Project>
<Name>Dapper.Contrib</Name>
</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">
<Project>{DAF737E1-05B5-4189-A5AA-DAC6233B64D7}</Project>
<Name>Dapper</Name>
......
......@@ -4,6 +4,9 @@
using System.Linq;
using System.Reflection;
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System;
using Dapper;
namespace Dapper.Contrib.Tests
......@@ -100,5 +103,52 @@ public void InsertGetUpdate()
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
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);
......@@ -51,9 +53,10 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(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);
......@@ -325,9 +328,10 @@ public static T GetInterfaceProxy<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);
......
<?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
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperTests NET35", "DapperTests NET35\DapperTests NET35.csproj", "{3BAA9F79-BA0A-4092-B47B-20170DD47989}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -83,6 +87,26 @@ Global
{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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
......@@ -22,6 +22,8 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin\Debug\Dapper.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
......@@ -36,7 +38,6 @@
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Data.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="SqlMapper.cs" />
......
......@@ -32,5 +32,5 @@
// 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.6.0.0")]
[assembly: AssemblyFileVersion("1.6.0.0")]
[assembly: AssemblyVersion("1.8.0.0")]
[assembly: AssemblyFileVersion("1.8.0.0")]
......@@ -19,11 +19,22 @@
namespace Dapper
{
/// <summary>
/// Dapper, a light weight object mapper for ADO.NET
/// </summary>
public static partial class SqlMapper
{
/// <summary>
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
/// </summary>
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 Action<IDbCommand, bool> GetBindByName(Type commandType)
......@@ -107,13 +118,23 @@ private Link(TKey key, TValue value, Link<TKey, TValue> tail)
}
class CacheInfo
{
public object Deserializer { get; set; }
public object[] OtherDeserializers { get; set; }
public Func<IDataReader, object> Deserializer { get; set; }
public Func<IDataReader, object>[] OtherDeserializers { get; set; }
public Action<IDbCommand, object> ParamReader { get; set; }
private int hitCount;
public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); }
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
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
......@@ -126,11 +147,14 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo 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
static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
private static void SetQueryCache(Identity key, CacheInfo value)
......@@ -155,6 +179,7 @@ private static void CollectCacheGarbage()
}
}
}
finally
{
Interlocked.Exchange(ref collect, 0);
......@@ -173,18 +198,30 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
value = null;
return false;
}
private static void PurgeQueryCache(Identity key)
/// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{
CacheInfo info;
_queryCache.TryRemove(key, out info);
_queryCache.Clear();
OnQueryCachePurged();
}
/// <summary>
/// Return a count of all the cached queries by dapper
/// </summary>
/// <returns></returns>
public static int GetCachedSQLCount()
{
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)
{
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
......@@ -192,6 +229,10 @@ public static int GetCachedSQLCount()
return data;
}
/// <summary>
/// Deep diagnostics only: find any hash collisions in the cache
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<int,int>> GetHashCollissions()
{
var counts = new Dictionary<int, int>();
......@@ -253,9 +294,9 @@ static SqlMapper()
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
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)
{
DbType dbType;
......@@ -269,6 +310,10 @@ private static DbType LookupDbType(Type type, string name)
{
return dbType;
}
if (type.FullName == LinqBinary)
{
return DbType.Binary;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
// 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)
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)
{
......@@ -290,6 +338,15 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int 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)
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0)
......@@ -320,20 +377,51 @@ private Identity(string sql, CommandType? commandType, string connectionString,
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
}
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
return Equals(obj as Identity);
}
internal readonly string sql;
internal readonly CommandType? commandType;
internal readonly int hashCode, gridIndex;
/// <summary>
/// The sql
/// </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;
internal readonly string connectionString;
internal readonly Type parametersType;
/// <summary>
///
/// </summary>
public readonly string connectionString;
/// <summary>
///
/// </summary>
public readonly Type parametersType;
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return hashCode;
}
/// <summary>
/// Compare 2 Identity objects
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(Identity other)
{
return
......@@ -414,9 +502,12 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object
}
// nice and simple
identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null);
info = GetCacheInfo(identity);
return ExecuteCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
if ((object)param != null)
{
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
/// <summary>
......@@ -431,7 +522,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
/// <summary>
/// Executes a query, returning the data typed as per T
/// </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
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
......@@ -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 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<T>(reader, 0, -1, false);
SetQueryCache(identity, info);
}
info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
SetQueryCache(identity, info);
return info.Deserializer;
};
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;
var next = deserializer(reader);
clean = true;
yield return next;
next = deserializer(reader);
}
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
/// <summary>
/// Maps a query to objects
/// </summary>
/// <typeparam name="T">The return type</typeparam>
/// <typeparam name="U"></typeparam>
/// <typeparam name="TFirst">The first type in the recordset</typeparam>
/// <typeparam name="TSecond">The second type in the recordset</typeparam>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
......@@ -531,6 +628,7 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
/// <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">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
#if CSHARP30
......@@ -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);
}
/// <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>(
#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
......@@ -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);
}
/// <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>(
#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
......@@ -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);
}
#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)
{
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 { }
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)
{
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 { }
ownedReader = ownedCommand.ExecuteReader();
reader = ownedReader;
}
object deserializer;
object[] otherDeserializers;
Func<IDataReader, object> deserializer = null;
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)
{
int current = 0;
cacheDeserializers();
}
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;
Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map);
Func<Type,int> nextSplit = type =>
if (mapIt != null)
{
while (reader.Read())
{
var currentSplit = splits[splitIndex];
if (splits.Length > splitIndex + 1)
TReturn next;
try
{
splitIndex++;
next = mapIt(reader);
}
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))
catch (DataException)
{
var props = GetSettableProps(type);
var fields = GetSettableFields(type);
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;
}
}
cacheDeserializers();
mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map);
next = mapIt(reader);
}
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;
yield return next;
}
if (typeof(TFourth) != typeof(DontMap))
}
}
finally
{
try
{
if (ownedReader != null)
{
var next = nextSplit(typeof(TFourth));
otherDeserializer.Add(GetDeserializer<TFourth>(reader, split, next - split, true));
split = next;
ownedReader.Dispose();
}
if (typeof(TFifth) != typeof(DontMap))
}
finally
{
if (ownedCommand != null)
{
var next = nextSplit(typeof(TFifth));
otherDeserializer.Add(GetDeserializer<TFifth>(reader, split, next - split, true));
ownedCommand.Dispose();
}
otherDeserializers = cinfo.OtherDeserializers = otherDeserializer.ToArray();
SetQueryCache(identity, cinfo);
}
}
}
var rootDeserializer = (Func<IDataReader, TFirst>)deserializer;
var deserializer2 = (Func<IDataReader, TSecond>)otherDeserializers[0];
private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
{
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 (otherDeserializers.Length > 2)
{
var deserializer4 = (Func<IDataReader, TFourth>)otherDeserializers[2];
if (otherDeserializers.Length == 3)
if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
{
mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)(rootDeserializer(r), deserializer2(r), deserializer3(r), deserializer4(r));
}
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
skipFirst = true;
startingPos = current;
break;
}
}
}
if (mapIt != null)
int pos;
for (pos = startingPos; pos < reader.FieldCount; pos++)
{
bool clean = true;
try
// some people like ID some id ... assuming case insensitive splits for now
if (splitOn == "*")
{
while (reader.Read())
{
clean = false;
TReturn next = mapIt(reader);
clean = true;
yield return next;
}
break;
}
finally
if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
{
if (!clean) PurgeQueryCache(identity);
if (skipFirst)
{
skipFirst = false;
}
else
{
break;
}
}
}
}
finally
current = pos;
return pos;
};
var deserializers = new List<Func<IDataReader, object>>();
int split = 0;
bool first = true;
foreach (var type in types)
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
if (type != typeof(DontMap))
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
int next = nextSplit(type);
deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
first = false;
split = next;
}
}
return deserializers.ToArray();
}
private static CacheInfo GetCacheInfo(Identity identity)
......@@ -777,7 +907,7 @@ private static CacheInfo GetCacheInfo(Identity identity)
{
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
{
......@@ -789,23 +919,22 @@ private static CacheInfo GetCacheInfo(Identity identity)
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
// dynamic is passed in as Object ... by c# design
if (type == typeof(object)
|| type == typeof(FastExpando))
{
return GetDynamicDeserializer<T>(reader, startBound, length, returnNullIfFirstMissing);
return GetDynamicDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
#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
......@@ -829,6 +958,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj
return data.TryGetValue(binder.Name, out result);
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return data.Keys;
}
#region IDictionary<string,object> Members
void IDictionary<string, object>.Add(string key, object value)
......@@ -869,7 +1003,11 @@ public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out obj
}
set
{
throw new NotImplementedException();
if (!data.ContainsKey(key))
{
throw new NotImplementedException();
}
data[key] = value;
}
}
......@@ -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;
if (length == -1)
......@@ -958,14 +1096,19 @@ IEnumerator IEnumerable.GetEnumerator()
row[r.GetName(i)] = tmp;
if (returnNullIfFirstMissing && i == startBound && tmp == null)
{
return default(T);
return null;
}
}
//we know this is an object so it will not box
return (T)(object)FastExpando.Attach(row);
return FastExpando.Attach(row);
};
}
#endif
/// <summary>
/// Internal use only
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
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");
return s[0];
}
/// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
public static char? ReadNullableChar(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");
return s[0];
}
/// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", true)]
public static void PackListParameters(IDbCommand command, string namePrefix, object value)
......@@ -997,6 +1147,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
if (list != null)
{
bool isString = value is IEnumerable<string>;
bool isDbString = value is IEnumerable<DbString>;
foreach (var item in list)
{
count++;
......@@ -1011,7 +1162,15 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
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)
......@@ -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));
}
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;
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 il = dm.GetILGenerator();
......@@ -1164,9 +1325,9 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
il.MarkLabel(lenDone);
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);
// relative stack [boxed value or DBNull]
......@@ -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();
}
}
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!)
#pragma warning disable 618
if (typeof(T) == typeof(char))
if (type == typeof(char))
{ // 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
return r =>
{
var val = r.GetValue(index);
if (val == DBNull.Value)
{
val = null;
}
return (T)val;
return val is DBNull ? null : val;
};
}
......@@ -1271,7 +1428,9 @@ static List<PropInfo> GetSettableProps(Type t)
.Select(p => new PropInfo
{
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
})
.Where(info => info.Setter != null)
......@@ -1283,24 +1442,33 @@ static List<FieldInfo> GetSettableFields(Type t)
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
IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#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
)
{
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();
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(T));
il.DeclareLocal(type);
bool haveEnumLocal = false;
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
var properties = GetSettableProps(typeof(T));
var fields = GetSettableFields(typeof(T));
var properties = GetSettableProps(type);
var fields = GetSettableFields(type);
if (length == -1)
{
length = reader.FieldCount - startBound;
......@@ -1329,9 +1497,27 @@ static List<FieldInfo> GetSettableFields(Type t)
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();
// stack is empty
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target]
if(type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
} else
{
il.Emit(OpCodes.Ldloc_1);// [target]
}
// stack is now [target]
bool first = true;
var allDone = il.DefineLabel();
foreach (var item in setters)
......@@ -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.Callvirt, getItem); // stack is now [target][target][value-as-object]
Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType;
if (memberType == typeof(char) || memberType == typeof(char?))
......@@ -1410,10 +1595,10 @@ static List<FieldInfo> GetSettableFields(Type t)
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.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
{
......@@ -1426,7 +1611,14 @@ static List<FieldInfo> GetSettableFields(Type t)
}
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
{
......@@ -1453,21 +1645,37 @@ static List<FieldInfo> GetSettableFields(Type t)
first = false;
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.BeginCatchBlock(typeof(Exception)); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
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.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);
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)
{
string name = "(n/a)", value = "(n/a)";
......@@ -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
{
private IDataReader reader;
......@@ -1534,14 +1745,21 @@ public IEnumerable<T> Read<T>()
if (consumed) throw new InvalidOperationException("Each grid can only be iterated once");
var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity);
var deserializer = (Func<IDataReader, T>)cache.Deserializer;
if (deserializer == null)
var deserializer = cache.Deserializer;
Func<Func<IDataReader, object>> deserializerGenerator = () =>
{
deserializer = GetDeserializer<T>(reader, 0, -1, false);
deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
cache.Deserializer = deserializer;
return deserializer;
};
if (deserializer == null)
{
deserializer = deserializerGenerator();
}
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)
......@@ -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
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
#else
......@@ -1576,6 +1803,16 @@ public IEnumerable<T> Read<T>()
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
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
#else
......@@ -1585,6 +1822,17 @@ public IEnumerable<T> Read<T>()
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
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else
......@@ -1595,6 +1843,18 @@ public IEnumerable<T> Read<T>()
}
#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")
{
......@@ -1602,26 +1862,27 @@ public IEnumerable<T> Read<T>()
}
#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
{
while (index == gridIndex && reader.Read())
{
clean = false;
T next = deserializer(reader);
clean = true;
yield return next;
object next;
try
{
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
{
if (!clean)
{
PurgeQueryCache(typedIdentity);
}
if (index == gridIndex)
{
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()
{
if (reader != null)
......@@ -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
{
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
List<object> templates;
class ParamInfo
{
......@@ -1672,28 +1943,75 @@ class ParamInfo
public IDbDataParameter AttachedParam { get; set; }
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
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)
{
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
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;
var idx = prop.GetIndexParameters();
if (idx != null && idx.Length != 0) continue;
Add(prop.Name, prop.GetValue(template, null), null, ParameterDirection.Input, null);
templates = templates ?? new List<object>();
templates.Add(obj);
}
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(
#if CSHARP30
string name, object value, DbType? dbType, ParameterDirection? direction, int? size
......@@ -1720,13 +2038,42 @@ static string Clean(string 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)
{
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;
p.ParameterName = param.Name;
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
var s = val as string;
......@@ -1745,11 +2092,32 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command)
{
p.DbType = param.DbType.Value;
}
command.Parameters.Add(p);
if (add)
{
command.Parameters.Add(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)
{
var val = parameters[Clean(name)].AttachedParam.Value;
......@@ -1764,13 +2132,37 @@ public T Get<T>(string name)
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
{
/// <summary>
/// Create a new DbString
/// </summary>
public DbString() { Length = -1; }
/// <summary>
/// Ansi vs Unicode
/// </summary>
public bool IsAnsi { get; set; }
/// <summary>
/// Fixed length
/// </summary>
public bool IsFixedLength { get; set; }
/// <summary>
/// Length of the string -1 for max
/// </summary>
public int Length { get; set; }
/// <summary>
/// The value of the string
/// </summary>
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)
{
if (IsFixedLength && Length == -1)
......
......@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata schemaVersion="2">
<id>Dapper</id>
<version>1.6</version>
<version>1.8</version>
<title>Dapper dot net</title>
<authors>Sam Saffron,Marc Gravell</authors>
<owners>Sam Saffron,Marc Gravell</owners>
......@@ -11,15 +11,24 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A high performance Micro-ORM supporting Sql Server, MySQL, Sqlite, SqlCE, Firebird etc..</description>
<summary>A high performance Micro-ORM</summary>
<tags>orm,sql,micro-orm</tags>
<tags>orm sql micro-orm</tags>
<frameworkAssemblies>
<frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="System.Data" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="System.Core"/>
<frameworkAssembly assemblyName="System"/>
<frameworkAssembly assemblyName="System.Data"/>
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
</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>
<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>
</package>
\ No newline at end of file
......@@ -183,7 +183,7 @@
<LastGenOutput>DataClasses.designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
......
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Diagnostics;
......@@ -9,6 +10,8 @@
using Massive;
using NHibernate.Criterion;
using NHibernate.Linq;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.SqlServer;
using SqlMapper.Linq2Sql;
using SqlMapper.NHibernate;
using Dapper.Contrib.Extensions;
......@@ -191,6 +194,11 @@ public void Run(int iterations)
// Soma
var somadb = new Soma.Core.Db(new SomaConfig());
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
......
......@@ -4,6 +4,7 @@
namespace SqlMapper
{
[ServiceStack.DataAnnotations.Alias("Posts")]
[Soma.Core.Table(Name = "Posts")]
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 @@
using System.IO;
using System.Data;
using System.Collections;
using System.Reflection;
namespace SqlMapper
{
class Tests
{
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()
{
connection.Query<int>("select 1 union all select 2 union all select 3")
......@@ -24,7 +101,7 @@ public void SelectListInt()
}
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()
{
......@@ -38,6 +115,40 @@ public void PassInEmptyIntArray()
.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()
{
connection.Query<int, int, int, Tuple<int, int, int>>(
......@@ -185,7 +296,7 @@ public void TestExecuteMultipleCommand()
class Student
{
public string Name {get; set;}
public string Name { get; set; }
public int Age { get; set; }
}
......@@ -224,7 +335,7 @@ class TestObj
internal int Internal { set { _internal = value; } }
public int _priv;
internal int Priv { set { _priv = value; } }
private int Priv { set { _priv = value; } }
}
public void TestSetInternal()
......@@ -287,6 +398,26 @@ public void TestEnumerationDynamic()
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
{
......@@ -374,7 +505,7 @@ public void TestMultiMapGridReader()
data[2].Owner.IsNull();
}
connection.Execute("drop table #Users drop table #Posts");
}
......@@ -757,7 +888,7 @@ public IntDynamicParam(IEnumerable<int> numbers)
this.numbers = numbers;
}
public void AddParameters(IDbCommand command)
public void AddParameters(IDbCommand command, Dapper.SqlMapper.Identity identity)
{
var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure;
......@@ -862,10 +993,12 @@ class WithBizarreData
public void TestUnexpectedDataMessage()
{
string msg = null;
try {
try
{
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;
}
......@@ -923,7 +1056,7 @@ public void TestInvalidSplitCausesNiceError()
connection.Query<User, User, User>("select 1 A, 2 B, 3 C", (x, y) => x);
}
catch (ArgumentException)
{
{
// expecting an app exception due to multi mapping being bodged
}
......@@ -989,19 +1122,19 @@ public void TestDynamicParamNullSupport()
var p = new DynamicParameters();
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();
}
class Foo1
{
class Foo1
{
public int Id;
public int BarId { get; set; }
}
}
class Bar1
{
public int BarId;
public string Name { get; set; }
public int BarId;
public string Name { get; set; }
}
public void TestMultiMapperIsNotConfusedWithUnorderedCols()
{
......@@ -1051,5 +1184,156 @@ public void TestWithNonPublicConstructor()
var output = connection.Query<WithPrivateConstructor>("select 1 as Foo").First();
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