1 Star 2 Fork 0

柏松/Dapper

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
SqlMapper.cs 183.02 KB
一键复制 编辑 原始数据 按行查看 历史
柏松 提交于 2017-12-25 12:03 . init
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328
/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: http://code.google.com/p/dapper-dot-net/
Note: to build on C# 3.0 + .NET 3.5, include the CSHARP30 compiler symbol (and yes,
I know the difference between language and runtime versions; this is a compromise).
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
namespace Dapper
{
/// <summary>
/// Additional state flags that control command behaviour
/// </summary>
[Flags]
public enum CommandFlags
{
/// <summary>
/// No additonal flags
/// </summary>
None = 0,
/// <summary>
/// Should data be buffered before returning?
/// </summary>
Buffered = 1,
/// <summary>
/// Can async queries be pipelined?
/// </summary>
Pipelined = 2,
}
/// <summary>
/// Represents the key aspects of a sql operation
/// </summary>
public struct CommandDefinition
{
private readonly string commandText;
private readonly object parameters;
private readonly IDbTransaction transaction;
private readonly int? commandTimeout;
private readonly CommandType? commandType;
private readonly CommandFlags flags;
/// <summary>
/// The command (sql or a stored-procedure name) to execute
/// </summary>
public string CommandText { get { return commandText; } }
/// <summary>
/// The parameters associated with the command
/// </summary>
public object Parameters { get { return parameters; } }
/// <summary>
/// The active transaction for the command
/// </summary>
public IDbTransaction Transaction { get { return transaction; } }
/// <summary>
/// The effective timeout for the command
/// </summary>
public int? CommandTimeout { get { return commandTimeout; } }
/// <summary>
/// The type of command that the command-text represents
/// </summary>
public CommandType? CommandType { get { return commandType; } }
/// <summary>
/// Should data be buffered before returning?
/// </summary>
public bool Buffered { get { return (flags & CommandFlags.Buffered) != 0; } }
/// <summary>
/// Additional state flags against this command
/// </summary>
public CommandFlags Flags { get { return flags; } }
/// <summary>
/// Can async queries be pipelined?
/// </summary>
public bool Pipelined { get { return (flags & CommandFlags.Pipelined) != 0; } }
/// <summary>
/// Initialize the command definition
/// </summary>
#if CSHARP30
public CommandDefinition(string commandText, object parameters, IDbTransaction transaction, int? commandTimeout,
CommandType? commandType, CommandFlags flags)
#else
public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered
#if ASYNC
, CancellationToken cancellationToken = default(CancellationToken)
#endif
)
#endif
{
this.commandText = commandText;
this.parameters = parameters;
this.transaction = transaction;
this.commandTimeout = commandTimeout;
this.commandType = commandType;
this.flags = flags;
#if ASYNC
this.cancellationToken = cancellationToken;
#endif
}
#if ASYNC
private readonly CancellationToken cancellationToken;
/// <summary>
/// For asynchronous operations, the cancellation-token
/// </summary>
public CancellationToken CancellationToken { get { return cancellationToken; } }
#endif
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
{
var cmd = cnn.CreateCommand();
var bindByName = GetBindByName(cmd.GetType());
if (bindByName != null) bindByName(cmd, true);
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = commandText;
if (commandTimeout.HasValue)
cmd.CommandTimeout = commandTimeout.Value;
if (commandType.HasValue)
cmd.CommandType = commandType.Value;
if (paramReader != null)
{
paramReader(cmd, parameters);
}
return cmd;
}
static SqlMapper.Link<Type, Action<IDbCommand, bool>> bindByNameCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType)
{
if (commandType == null) return null; // GIGO
Action<IDbCommand, bool> action;
if (SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
{
return action;
}
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
action = null;
ParameterInfo[] indexers;
MethodInfo setter;
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
&& (setter = prop.GetSetMethod()) != null
)
{
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, setter, null);
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
}
// cache it
SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
return action;
}
}
/// <summary>
/// Dapper, a light weight object mapper for ADO.NET
/// </summary>
static partial class SqlMapper
{
/// <summary>
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
/// </summary>
public partial interface IDynamicParameters
{
/// <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);
}
/// <summary>
/// Extends IDynamicParameters providing by-name lookup of parameter values
/// </summary>
public interface IParameterLookup : IDynamicParameters
{
/// <summary>
/// Get the value of the specified parameter (return null if not found)
/// </summary>
object this[string name] { get; }
}
/// <summary>
/// Implement this interface to pass an arbitrary db specific parameter to Dapper
/// </summary>
public interface ICustomQueryParameter
{
/// <summary>
/// Add the parameter needed to the command before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="name">Parameter name</param>
void AddParameter(IDbCommand command, string name);
}
/// <summary>
/// Implement this interface to change default mapping of reader columns to type memebers
/// </summary>
public interface ITypeMap
{
/// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
ConstructorInfo FindConstructor(string[] names, Type[] types);
/// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName);
/// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap GetMember(string columnName);
}
/// <summary>
/// Implements this interface to provide custom member mapping
/// </summary>
public interface IMemberMap
{
/// <summary>
/// Source DataReader column name
/// </summary>
string ColumnName { get; }
/// <summary>
/// Target member type
/// </summary>
Type MemberType { get; }
/// <summary>
/// Target property
/// </summary>
PropertyInfo Property { get; }
/// <summary>
/// Target field
/// </summary>
FieldInfo Field { get; }
/// <summary>
/// Target constructor parameter
/// </summary>
ParameterInfo Parameter { get; }
}
/// <summary>
/// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
/// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
/// equality. The type is fully thread-safe.
/// </summary>
internal partial class Link<TKey, TValue> where TKey : class
{
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
{
while (link != null)
{
if ((object)key == (object)link.Key)
{
value = link.Value;
return true;
}
link = link.Tail;
}
value = default(TValue);
return false;
}
public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value)
{
bool tryAgain;
do
{
var snapshot = Interlocked.CompareExchange(ref head, null, null);
TValue found;
if (TryGet(snapshot, key, out found))
{ // existing match; report the existing value instead
value = found;
return false;
}
var newNode = new Link<TKey, TValue>(key, value, snapshot);
// did somebody move our cheese?
tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot;
} while (tryAgain);
return true;
}
private Link(TKey key, TValue value, Link<TKey, TValue> tail)
{
Key = key;
Value = value;
Tail = tail;
}
public TKey Key { get; private set; }
public TValue Value { get; private set; }
public Link<TKey, TValue> Tail { get; private set; }
}
partial class CacheInfo
{
public DeserializerState 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); }
}
static int GetColumnHash(IDataReader reader)
{
unchecked
{
int colCount = reader.FieldCount, hash = colCount;
for (int i = 0; i < colCount; i++)
{ // binding code is only interested in names - not types
object tmp = reader.GetName(i);
hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode());
}
return hash;
}
}
struct DeserializerState
{
public readonly int Hash;
public readonly Func<IDataReader, object> Func;
public DeserializerState(int hash, Func<IDataReader, object> func)
{
Hash = hash;
Func = func;
}
}
/// <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
// ReaderWriterLockSlim etc; a simple lock is faster
private static void SetQueryCache(Identity key, CacheInfo value)
{
lock (_queryCache) { _queryCache[key] = value; }
}
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
}
private static void PurgeQueryCacheByType(Type type)
{
lock (_queryCache)
{
var toRemove = _queryCache.Keys.Where(id => id.type == type).ToArray();
foreach (var key in toRemove)
_queryCache.Remove(key);
}
}
/// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{
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)
{
if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
{
CollectCacheGarbage();
}
_queryCache[key] = value;
}
private static void CollectCacheGarbage()
{
try
{
foreach (var pair in _queryCache)
{
if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
{
CacheInfo cache;
_queryCache.TryRemove(pair.Key, out cache);
}
}
}
finally
{
Interlocked.Exchange(ref collect, 0);
}
}
private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
private static int collect;
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
if (_queryCache.TryGetValue(key, out value))
{
value.RecordHit();
return true;
}
value = null;
return false;
}
/// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{
_queryCache.Clear();
OnQueryCachePurged();
}
private static void PurgeQueryCacheByType(Type type)
{
foreach (var entry in _queryCache)
{
CacheInfo cache;
if (entry.Key.type == type)
_queryCache.TryRemove(entry.Key, out cache);
}
}
/// <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()));
if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
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>();
foreach (var key in _queryCache.Keys)
{
int count;
if (!counts.TryGetValue(key.hashCode, out count))
{
counts.Add(key.hashCode, 1);
}
else
{
counts[key.hashCode] = count + 1;
}
}
return from pair in counts
where pair.Value > 1
select Tuple.Create(pair.Key, pair.Value);
}
#endif
static readonly Dictionary<Type, DbType> typeMap;
static SqlMapper()
{
typeMap = new Dictionary<Type, DbType>();
typeMap[typeof(byte)] = DbType.Byte;
typeMap[typeof(sbyte)] = DbType.SByte;
typeMap[typeof(short)] = DbType.Int16;
typeMap[typeof(ushort)] = DbType.UInt16;
typeMap[typeof(int)] = DbType.Int32;
typeMap[typeof(uint)] = DbType.UInt32;
typeMap[typeof(long)] = DbType.Int64;
typeMap[typeof(ulong)] = DbType.UInt64;
typeMap[typeof(float)] = DbType.Single;
typeMap[typeof(double)] = DbType.Double;
typeMap[typeof(decimal)] = DbType.Decimal;
typeMap[typeof(bool)] = DbType.Boolean;
typeMap[typeof(string)] = DbType.String;
typeMap[typeof(char)] = DbType.StringFixedLength;
typeMap[typeof(Guid)] = DbType.Guid;
typeMap[typeof(DateTime)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan)] = DbType.Time;
typeMap[typeof(byte[])] = DbType.Binary;
typeMap[typeof(byte?)] = DbType.Byte;
typeMap[typeof(sbyte?)] = DbType.SByte;
typeMap[typeof(short?)] = DbType.Int16;
typeMap[typeof(ushort?)] = DbType.UInt16;
typeMap[typeof(int?)] = DbType.Int32;
typeMap[typeof(uint?)] = DbType.UInt32;
typeMap[typeof(long?)] = DbType.Int64;
typeMap[typeof(ulong?)] = DbType.UInt64;
typeMap[typeof(float?)] = DbType.Single;
typeMap[typeof(double?)] = DbType.Double;
typeMap[typeof(decimal?)] = DbType.Decimal;
typeMap[typeof(bool?)] = DbType.Boolean;
typeMap[typeof(char?)] = DbType.StringFixedLength;
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan?)] = DbType.Time;
typeMap[typeof(object)] = DbType.Object;
}
/// <summary>
/// Configire the specified type to be mapped to a given db-type
/// </summary>
public static void AddTypeMap(Type type, DbType dbType)
{
typeMap[type] = dbType;
}
internal const string LinqBinary = "System.Data.Linq.Binary";
internal static DbType LookupDbType(Type type, string name)
{
DbType dbType;
var nullUnderlyingType = Nullable.GetUnderlyingType(type);
if (nullUnderlyingType != null) type = nullUnderlyingType;
if (type.IsEnum && !typeMap.ContainsKey(type))
{
type = Enum.GetUnderlyingType(type);
}
if (typeMap.TryGetValue(type, out dbType))
{
return dbType;
}
if (type.FullName == LinqBinary)
{
return DbType.Binary;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
return DynamicParameters.EnumerableMultiParameter;
}
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
}
/// <summary>
/// Identity of a cached query in Dapper, used for extensability
/// </summary>
public partial class Identity : IEquatable<Identity>
{
internal Identity ForGrid(Type primaryType, int gridIndex)
{
return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex);
}
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)
{ }
private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex)
{
this.sql = sql;
this.commandType = commandType;
this.connectionString = connectionString;
this.type = type;
this.parametersType = parametersType;
this.gridIndex = gridIndex;
unchecked
{
hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
hashCode = hashCode * 23 + commandType.GetHashCode();
hashCode = hashCode * 23 + gridIndex.GetHashCode();
hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
if (otherTypes != null)
{
foreach (var t in otherTypes)
{
hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode());
}
}
hashCode = hashCode * 23 + (connectionString == null ? 0 : SqlMapper.connectionStringComparer.GetHashCode(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);
}
/// <summary>
/// The sql
/// </summary>
public readonly string sql;
/// <summary>
/// The command type
/// </summary>
public readonly CommandType? commandType;
/// <summary>
///
/// </summary>
public readonly int hashCode, gridIndex;
/// <summary>
///
/// </summary>
public readonly Type type;
/// <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
other != null &&
gridIndex == other.gridIndex &&
type == other.type &&
sql == other.sql &&
commandType == other.commandType &&
SqlMapper.connectionStringComparer.Equals(connectionString, other.connectionString) &&
parametersType == other.parametersType;
}
}
#if CSHARP30
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param)
{
return Execute(cnn, sql, param, null, null, null);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Execute(cnn, sql, param, transaction, null, null);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Execute(cnn, sql, param, null, null, commandType);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Execute(cnn, sql, param, transaction, null, commandType);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param)
{
return ExecuteReader(cnn, sql, param, null, null, null);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return ExecuteReader(cnn, sql, param, transaction, null, null);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return ExecuteReader(cnn, sql, param, null, null, commandType);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return ExecuteReader(cnn, sql, param, transaction, null, commandType);
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <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>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param)
{
return Query<T>(cnn, sql, param, null, true, null, null);
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <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>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query<T>(cnn, sql, param, transaction, true, null, null);
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <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>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Query<T>(cnn, sql, param, null, true, null, commandType);
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <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>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Query<T>(cnn, sql, param, transaction, true, null, commandType);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return QueryMultiple(cnn, sql, param, transaction, null, null);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return QueryMultiple(cnn, sql, param, null, null, commandType);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return QueryMultiple(cnn, sql, param, transaction, null, commandType);
}
#endif
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteImpl(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int ExecuteScalar(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteImpl(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteImpl(cnn, ref command);
}
private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = param as IEnumerable;
Identity identity;
CacheInfo info = null;
if (multiExec != null && !(multiExec is string))
{
#if ASYNC
if((command.Flags & CommandFlags.Pipelined) != 0)
{
// this includes all the code for concurrent/overlapped query
return ExecuteMultiImplAsync(cnn, command, multiExec).Result;
}
#endif
bool isFirst = true;
int total = 0;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) cnn.Open();
using (var cmd = command.SetupCommand(cnn, null))
{
string masterSql = null;
foreach (var obj in multiExec)
{
if (isFirst)
{
masterSql = cmd.CommandText;
isFirst = false;
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj);
}
else
{
cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
cmd.Parameters.Clear(); // current code is Add-tastic
}
info.ParamReader(cmd, obj);
total += cmd.ExecuteNonQuery();
}
}
}
finally
{
if (wasClosed) cnn.Close();
}
return total;
}
// nice and simple
if (param != null)
{
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param);
}
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
/// <remarks>
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
/// or <see cref="DataSet"/>.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// DataTable table = new DataTable("MyTable");
/// using (var reader = ExecuteReader(cnn, sql, param))
/// {
/// table.Load(reader);
/// }
/// ]]>
/// </code>
/// </example>
public static IDataReader ExecuteReader(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteReaderImpl(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
/// <remarks>
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
/// or <see cref="DataSet"/>.
/// </remarks>
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteReaderImpl(cnn, ref command);
}
#if !CSHARP30
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
return Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
}
#else
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param)
{
return Query(cnn, sql, param, null, true, null, null);
}
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query(cnn, sql, param, transaction, true, null, null);
}
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, CommandType? commandType)
{
return Query(cnn, sql, param, null, true, null, commandType);
}
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType? commandType)
{
return Query(cnn, sql, param, transaction, true, null, commandType);
}
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType)
{
return Query<IDictionary<string, object>>(cnn, sql, param, transaction, buffered, commandTimeout, commandType);
}
#endif
/// <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>
/// <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>
public static IEnumerable<T> Query<T>(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<T>(cnn, command);
return command.Buffered ? data.ToList() : data;
}
/// </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>
/// <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>
public static int QuerySingle<T>(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = Execute(cnn, command);
return data;//command.Buffered ? data.ToList() : data;
}
/// <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>
/// <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>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command)
{
var data = QueryImpl<T>(cnn, command);
return command.Buffered ? data.ToList() : data;
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return QueryMultipleImpl(cnn, ref command);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command)
{
return QueryMultipleImpl(cnn, ref command);
}
private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity, param);
IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) cnn.Open();
cmd = command.SetupCommand(cnn, info.ParamReader);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
var result = new GridReader(cmd, reader, identity);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
// in the connection closing itself
return result;
}
catch
{
if (reader != null)
{
if (!reader.IsClosed) try { cmd.Cancel(); }
catch { /* don't spoil the existing exception */ }
reader.Dispose();
}
if (cmd != null) cmd.Dispose();
if (wasClosed) cnn.Close();
throw;
}
}
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param);
IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
// in the connection closing itself
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
SetQueryCache(identity, info);
}
var func = tuple.Func;
while (reader.Read())
{
object val = func(reader);
if (val == null || val is T)
{
yield return (T)val;
}
else
{
yield return (T)Convert.ChangeType(val, typeof(T));
}
}
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
}
finally
{
if (reader != null)
{
if (!reader.IsClosed) try { cmd.Cancel(); }
catch { /* don't spoil the existing exception */ }
reader.Dispose();
}
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
}
/// <summary>
/// Maps a query to objects
/// </summary>
/// <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>
/// <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">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
#if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, DontMap, DontMap, 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
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, 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
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, 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, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
/// <summary>
/// Perform a multi mapping query with 6 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="TSixth"></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, TSixth, TReturn>(
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, 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, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
/// <summary>
/// Perform a multi mapping query with 7 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="TSixth"></typeparam>
/// <typeparam name="TSeventh"></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, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, 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, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
#endif
partial class DontMap { }
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null);
return buffered ? results.ToList() : results;
}
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity)
{
object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
CacheInfo cinfo = GetCacheInfo(identity, param);
IDbCommand ownedCommand = null;
IDataReader ownedReader = null;
bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;
try
{
if (reader == null)
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader();
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
Func<IDataReader, object>[] otherDeserializers = null;
int hash = GetColumnHash(reader);
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
{
var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
SetQueryCache(identity, cinfo);
}
Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map);
if (mapIt != null)
{
while (reader.Read())
{
yield return mapIt(reader);
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
if (wasClosed) cnn.Close();
}
}
}
private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, 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));
case 5:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r));
case 6:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r));
#endif
default:
throw new NotSupportedException();
}
}
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
var deserializers = new List<Func<IDataReader, object>>();
var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray();
bool isMultiSplit = splits.Length > 1;
if (types.First() == typeof(Object))
{
// we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations
// is supported
bool first = true;
int currentPos = 0;
int splitIdx = 0;
string currentSplit = splits[splitIdx];
foreach (var type in types)
{
if (type == typeof(DontMap))
{
break;
}
int splitPoint = GetNextSplitDynamic(currentPos, currentSplit, reader);
if (isMultiSplit && splitIdx < splits.Length - 1)
{
currentSplit = splits[++splitIdx];
}
deserializers.Add((GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first)));
currentPos = splitPoint;
first = false;
}
}
else
{
// in this we go right to left through the data reader in order to cope with properties that are
// named the same as a subsequent primary key that we split on
int currentPos = reader.FieldCount;
int splitIdx = splits.Length - 1;
var currentSplit = splits[splitIdx];
for (var typeIdx = types.Length - 1; typeIdx >= 0; --typeIdx)
{
var type = types[typeIdx];
if (type == typeof(DontMap))
{
continue;
}
int splitPoint = 0;
if (typeIdx > 0)
{
splitPoint = GetNextSplit(currentPos, currentSplit, reader);
if (isMultiSplit && splitIdx > 0)
{
currentSplit = splits[--splitIdx];
}
}
deserializers.Add((GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0)));
currentPos = splitPoint;
}
deserializers.Reverse();
}
return deserializers.ToArray();
}
private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader reader)
{
if (startIdx == reader.FieldCount)
{
throw new ArgumentException(MultiMapSplitExceptionMessage);
}
if (splitOn == "*")
{
return ++startIdx;
}
for (var i = startIdx + 1; i < reader.FieldCount; ++i)
{
if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
return reader.FieldCount;
}
private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader)
{
if (splitOn == "*")
{
return --startIdx;
}
for (var i = startIdx - 1; i > 0; --i)
{
if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
throw new ArgumentException(MultiMapSplitExceptionMessage);
}
private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters)
{
CacheInfo info;
if (!TryGetQueryCache(identity, out info))
{
info = new CacheInfo();
if (identity.parametersType != null)
{
if (exampleParameters is IDynamicParameters)
{
info.ParamReader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
}
#if !CSHARP30
// special-case dictionary && `dynamic`
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>> && exampleParameters is System.Dynamic.IDynamicMetaObjectProvider)
{
info.ParamReader = (cmd, obj) =>
{
IDynamicParameters mapped = new DynamicParameters(obj);
mapped.AddParameters(cmd, identity);
};
}
#endif
else
{
var literals = GetLiteralTokens(identity.sql);
info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals);
}
}
SetQueryCache(identity, info);
}
return info;
}
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
#if !CSHARP30
// dynamic is passed in as Object ... by c# design
if (type == typeof(object)
|| type == typeof(DapperRow))
{
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
#else
if (type.IsAssignableFrom(typeof(Dictionary<string, object>)))
{
return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
#endif
Type underlyingType = null;
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
{
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
return GetStructDeserializer(type, underlyingType ?? type, startBound);
}
#if !CSHARP30
private sealed partial class DapperTable
{
string[] fieldNames;
readonly Dictionary<string, int> fieldNameLookup;
internal string[] FieldNames { get { return fieldNames; } }
public DapperTable(string[] fieldNames)
{
if (fieldNames == null) throw new ArgumentNullException("fieldNames");
this.fieldNames = fieldNames;
fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal);
// if there are dups, we want the **first** key to be the "winner" - so iterate backwards
for (int i = fieldNames.Length - 1; i >= 0; i--)
{
string key = fieldNames[i];
if (key != null) fieldNameLookup[key] = i;
}
}
internal int IndexOfName(string name)
{
int result;
return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1;
}
internal int AddField(string name)
{
if (name == null) throw new ArgumentNullException("name");
if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);
int oldLen = fieldNames.Length;
Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
fieldNames[oldLen] = name;
fieldNameLookup[name] = oldLen;
return oldLen;
}
internal bool FieldExists(string key)
{
return key != null && fieldNameLookup.ContainsKey(key);
}
public int FieldCount { get { return fieldNames.Length; } }
}
sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject
{
static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();
static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });
public DapperRowMetaObject(
System.Linq.Expressions.Expression expression,
System.Dynamic.BindingRestrictions restrictions
)
: base(expression, restrictions)
{
}
public DapperRowMetaObject(
System.Linq.Expressions.Expression expression,
System.Dynamic.BindingRestrictions restrictions,
object value
)
: base(expression, restrictions, value)
{
}
System.Dynamic.DynamicMetaObject CallMethod(
MethodInfo method,
System.Linq.Expressions.Expression[] parameters
)
{
var callMethod = new System.Dynamic.DynamicMetaObject(
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.Convert(Expression, LimitType),
method,
parameters),
System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType)
);
return callMethod;
}
public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name)
};
var callMethod = CallMethod(getValueMethod, parameters);
return callMethod;
}
// Needed for Visual basic dynamic support
public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name)
};
var callMethod = CallMethod(getValueMethod, parameters);
return callMethod;
}
public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name),
value.Expression,
};
var callMethod = CallMethod(setValueMethod, parameters);
return callMethod;
}
}
private sealed partial class DapperRow
: System.Dynamic.IDynamicMetaObjectProvider
, IDictionary<string, object>
{
readonly DapperTable table;
object[] values;
public DapperRow(DapperTable table, object[] values)
{
if (table == null) throw new ArgumentNullException("table");
if (values == null) throw new ArgumentNullException("values");
this.table = table;
this.values = values;
}
private sealed class DeadValue
{
public static readonly DeadValue Default = new DeadValue();
private DeadValue() { }
}
int ICollection<KeyValuePair<string, object>>.Count
{
get
{
int count = 0;
for (int i = 0; i < values.Length; i++)
{
if (!(values[i] is DeadValue)) count++;
}
return count;
}
}
public bool TryGetValue(string name, out object value)
{
var index = table.IndexOfName(name);
if (index < 0)
{ // doesn't exist
value = null;
return false;
}
// exists, **even if** we don't have a value; consider table rows heterogeneous
value = index < values.Length ? values[index] : null;
if (value is DeadValue)
{ // pretend it isn't here
value = null;
return false;
}
return true;
}
public override string ToString()
{
var sb = new StringBuilder("{DapperRow");
foreach (var kv in this)
{
var value = kv.Value;
sb.Append(", ").Append(kv.Key);
if (value != null)
{
sb.Append(" = '").Append(kv.Value).Append('\'');
}
else
{
sb.Append(" = NULL");
}
}
return sb.Append('}').ToString();
}
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
System.Linq.Expressions.Expression parameter)
{
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
var names = table.FieldNames;
for (var i = 0; i < names.Length; i++)
{
object value = i < values.Length ? values[i] : null;
if (!(value is DeadValue))
{
yield return new KeyValuePair<string, object>(names[i], value);
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#region Implementation of ICollection<KeyValuePair<string,object>>
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
IDictionary<string, object> dic = this;
dic.Add(item.Key, item.Value);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{ // removes values for **this row**, but doesn't change the fundamental table
for (int i = 0; i < values.Length; i++)
values[i] = DeadValue.Default;
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
object value;
return TryGetValue(item.Key, out value) && Equals(value, item.Value);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
foreach (var kv in this)
{
array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
}
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
IDictionary<string, object> dic = this;
return dic.Remove(item.Key);
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return false; }
}
#endregion
#region Implementation of IDictionary<string,object>
bool IDictionary<string, object>.ContainsKey(string key)
{
int index = table.IndexOfName(key);
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
return true;
}
void IDictionary<string, object>.Add(string key, object value)
{
SetValue(key, value, true);
}
bool IDictionary<string, object>.Remove(string key)
{
int index = table.IndexOfName(key);
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
values[index] = DeadValue.Default;
return true;
}
object IDictionary<string, object>.this[string key]
{
get { object val; TryGetValue(key, out val); return val; }
set { SetValue(key, value, false); }
}
public object SetValue(string key, object value)
{
return SetValue(key, value, false);
}
private object SetValue(string key, object value, bool isAdd)
{
if (key == null) throw new ArgumentNullException("key");
int index = table.IndexOfName(key);
if (index < 0)
{
index = table.AddField(key);
}
else if (isAdd && index < values.Length && !(values[index] is DeadValue))
{
// then semantically, this value already exists
throw new ArgumentException("An item with the same key has already been added", "key");
}
int oldLength = values.Length;
if (oldLength <= index)
{
// we'll assume they're doing lots of things, and
// grow it to the full width of the table
Array.Resize(ref values, table.FieldCount);
for (int i = oldLength; i < values.Length; i++)
{
values[i] = DeadValue.Default;
}
}
return values[index] = value;
}
ICollection<string> IDictionary<string, object>.Keys
{
get { return this.Select(kv => kv.Key).ToArray(); }
}
ICollection<object> IDictionary<string, object>.Values
{
get { return this.Select(kv => kv.Value).ToArray(); }
}
#endregion
}
#endif
private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id";
#if !CSHARP30
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var fieldCount = reader.FieldCount;
if (length == -1)
{
length = fieldCount - startBound;
}
if (fieldCount <= startBound)
{
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
}
var effectiveFieldCount = Math.Min(fieldCount - startBound, length);
DapperTable table = null;
return
r =>
{
if (table == null)
{
string[] names = new string[effectiveFieldCount];
for (int i = 0; i < effectiveFieldCount; i++)
{
names[i] = r.GetName(i + startBound);
}
table = new DapperTable(names);
}
var values = new object[effectiveFieldCount];
if (returnNullIfFirstMissing)
{
values[0] = r.GetValue(startBound);
if (values[0] is DBNull)
{
return null;
}
}
if (startBound == 0)
{
r.GetValues(values);
for (int i = 0; i < values.Length; i++)
if (values[i] is DBNull) values[i] = null;
}
else
{
var begin = returnNullIfFirstMissing ? 1 : 0;
for (var iter = begin; iter < effectiveFieldCount; ++iter)
{
object obj = r.GetValue(iter + startBound);
values[iter] = obj is DBNull ? null : obj;
}
}
return new DapperRow(table, values);
};
}
#else
internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var fieldCount = reader.FieldCount;
if (length == -1)
{
length = fieldCount - startBound;
}
if (fieldCount <= startBound)
{
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
}
return
r =>
{
IDictionary<string, object> row = new Dictionary<string, object>(length);
for (var i = startBound; i < startBound + length; i++)
{
var tmp = r.GetValue(i);
tmp = tmp == DBNull.Value ? null : tmp;
row[r.GetName(i)] = tmp;
if (returnNullIfFirstMissing && i == startBound && tmp == null)
{
return null;
}
}
return 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)
{
if (value == null || value is DBNull) throw new ArgumentNullException("value");
string s = value as string;
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)
{
if (value == null || value is DBNull) return null;
string s = value as string;
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 IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name)
{
IDbDataParameter result;
if (parameters.Contains(name))
{
result = (IDbDataParameter)parameters[name];
}
else
{
result = command.CreateParameter();
result.ParameterName = name;
parameters.Add(result);
}
return result;
}
/// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
public static void PackListParameters(IDbCommand command, string namePrefix, object value)
{
// initially we tried TVP, however it performs quite poorly.
// keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare
var list = value as IEnumerable;
var count = 0;
if (list != null)
{
if (FeatureSupport.Get(command.Connection).Arrays)
{
var arrayParm = command.CreateParameter();
arrayParm.Value = list;
arrayParm.ParameterName = namePrefix;
command.Parameters.Add(arrayParm);
}
else
{
bool isString = value is IEnumerable<string>;
bool isDbString = value is IEnumerable<DbString>;
foreach (var item in list)
{
count++;
var listParam = command.CreateParameter();
listParam.ParameterName = namePrefix + count;
listParam.Value = item ?? DBNull.Value;
if (isString)
{
listParam.Size = 4000;
if (item != null && ((string)item).Length > 4000)
{
listParam.Size = -1;
}
}
if (isDbString && item as DbString != null)
{
var str = item as DbString;
str.AddParameter(command, listParam.ParameterName);
}
else
{
command.Parameters.Add(listParam);
}
}
var regexIncludingUnknown = @"([?@:]" + Regex.Escape(namePrefix) + @")(\s+(?i)unknown(?-i))?";
if (count == 0)
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
{
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// looks like an optimize hint; leave it alone!
return match.Value;
}
else
{
return "(SELECT " + variableName + " WHERE 1 = 0)";
}
});
var dummyParam = command.CreateParameter();
dummyParam.ParameterName = namePrefix;
dummyParam.Value = DBNull.Value;
command.Parameters.Add(dummyParam);
}
else
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
{
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// looks like an optimize hint; expand it
var suffix = match.Groups[2].Value;
var sb = new StringBuilder(variableName).Append(1).Append(suffix);
for (int i = 2; i <= count; i++)
{
sb.Append(',').Append(variableName).Append(i).Append(suffix);
}
return sb.ToString();
}
else
{
var sb = new StringBuilder("(").Append(variableName).Append(1);
for (int i = 2; i <= count; i++)
{
sb.Append(',').Append(variableName).Append(i);
}
return sb.Append(')').ToString();
}
});
}
}
}
}
private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
{
return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
}
// look for ? / @ / : *by itself*
static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
literalTokens = new Regex(@"\{=([a-zA-Z0-9_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
/// <summary>
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
/// </summary>
internal struct LiteralToken
{
private readonly string token, member;
/// <summary>
/// The text in the original command that should be replaced
/// </summary>
public string Token { get { return token; } }
/// <summary>
/// The name of the member referred to by the token
/// </summary>
public string Member { get { return member; } }
internal LiteralToken(string token, string member)
{
this.token = token;
this.member = member;
}
internal static readonly IList<LiteralToken> None = new LiteralToken[0];
}
/// <summary>
/// Replace all literal tokens with their text form
/// </summary>
public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command)
{
var tokens = GetLiteralTokens(command.CommandText);
if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens);
}
internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static);
/// <summary>
/// Convert numeric values to their string form for SQL literal purposes
/// </summary>
[Obsolete("This is intended for internal usage only")]
public static string Format(object value)
{
if (value == null)
{
return "null";
}
else
{
switch (Type.GetTypeCode(value.GetType()))
{
case TypeCode.DBNull:
return "null";
case TypeCode.Boolean:
return ((bool)value) ? "1" : "0";
case TypeCode.Byte:
return ((byte)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.SByte:
return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt16:
return ((ushort)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int16:
return ((short)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt32:
return ((uint)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int32:
return ((int)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt64:
return ((ulong)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int64:
return ((long)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Single:
return ((float)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Double:
return ((double)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Decimal:
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
default:
if (value is IEnumerable && !(value is string))
{
var sb = new StringBuilder();
bool first = true;
foreach (object subval in (IEnumerable)value)
{
sb.Append(first ? '(' : ',').Append(Format(subval));
first = false;
}
if (first)
{
return "(select null where 1=0)";
}
else
{
return sb.Append(')').ToString();
}
}
throw new NotSupportedException(value.GetType().Name);
}
}
}
internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList<LiteralToken> tokens)
{
var sql = command.CommandText;
foreach (var token in tokens)
{
object value = parameters[token.Member];
#pragma warning disable 0618
string text = Format(value);
#pragma warning restore 0618
sql = sql.Replace(token.Token, text);
}
command.CommandText = sql;
}
internal static IList<LiteralToken> GetLiteralTokens(string sql)
{
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
if (!literalTokens.IsMatch(sql)) return LiteralToken.None;
var matches = literalTokens.Matches(sql);
var found = new HashSet<string>(StringComparer.InvariantCulture);
List<LiteralToken> list = new List<LiteralToken>(matches.Count);
foreach (Match match in matches)
{
string token = match.Value;
if (found.Add(match.Value))
{
list.Add(new LiteralToken(token, match.Groups[1].Value));
}
}
return list.Count == 0 ? LiteralToken.None : list;
}
/// <summary>
/// Internal use only
/// </summary>
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)
{
return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql));
}
internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
{
Type type = identity.parametersType;
bool filterParams = false;
if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)
{
filterParams = !smellsLikeOleDb.IsMatch(identity.sql);
}
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
var il = dm.GetILGenerator();
il.DeclareLocal(type); // 0
bool haveInt32Arg1 = false;
il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
il.Emit(OpCodes.Stloc_0);// stack is now empty
il.Emit(OpCodes.Ldarg_0); // stack is now [command]
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]
var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();
var ctors = type.GetConstructors();
ParameterInfo[] ctorParams;
IEnumerable<PropertyInfo> props = null;
// try to detect tuple patterns, e.g. anon-types, and use that to choose the order
// otherwise: alphabetical
if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length)
{
// check if reflection was kind enough to put everything in the right order for us
bool ok = true;
for (int i = 0; i < propsArr.Length; i++)
{
if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.InvariantCultureIgnoreCase))
{
ok = false;
break;
}
}
if (ok)
{
// pre-sorted; the reflection gods have smiled upon us
props = propsArr;
}
else
{ // might still all be accounted for; check the hard way
var positionByName = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
foreach (var param in ctorParams)
{
positionByName[param.Name] = param.Position;
}
if (positionByName.Count == propsArr.Length)
{
int[] positions = new int[propsArr.Length];
ok = true;
for (int i = 0; i < propsArr.Length; i++)
{
int pos;
if (!positionByName.TryGetValue(propsArr[i].Name, out pos))
{
ok = false;
break;
}
positions[i] = pos;
}
if (ok)
{
Array.Sort(positions, propsArr);
props = propsArr;
}
}
}
}
if (props == null) props = propsArr.OrderBy(x => x.Name);
if (filterParams)
{
props = FilterParameters(props, identity.sql);
}
foreach (var prop in props)
{
if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))
{
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("AddParameter"), null); // stack is now [parameters]
continue;
}
DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
if (dbType == DynamicParameters.EnumerableMultiParameter)
{
// this actually represents special handling for list types;
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
if (prop.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
}
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
continue;
}
il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
if (checkForDuplicates)
{
// need to be a little careful about adding; use a utility method
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name]
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("FindOrAddParameter"), null); // stack is [parameters] [parameter]
}
else
{
// no risk of duplicates; just blindly add
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]
il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
}
if (dbType != DbType.Time) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
{
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
}
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir]
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value]
bool checkForNull = true;
if (prop.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value]
if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
{ // struct but not Nullable<T>; boxed value cannot be null
checkForNull = false;
}
}
if (checkForNull)
{
if (dbType == DbType.String && !haveInt32Arg1)
{
il.DeclareLocal(typeof(int));
haveInt32Arg1 = true;
}
// relative stack: [boxed value]
il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
Label notNull = il.DefineLabel();
Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null;
il.Emit(OpCodes.Brtrue_S, notNull);
// relative stack [boxed value = null]
il.Emit(OpCodes.Pop); // relative stack empty
il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
if (dbType == DbType.String)
{
EmitInt32(il, 0);
il.Emit(OpCodes.Stloc_1);
}
if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
il.MarkLabel(notNull);
if (prop.PropertyType == typeof(string))
{
il.Emit(OpCodes.Dup); // [string] [string]
il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
EmitInt32(il, 4000); // [string] [length] [4000]
il.Emit(OpCodes.Cgt); // [string] [0 or 1]
Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, isLong);
EmitInt32(il, 4000); // [string] [4000]
il.Emit(OpCodes.Br_S, lenDone);
il.MarkLabel(isLong);
EmitInt32(il, -1); // [string] [-1]
il.MarkLabel(lenDone);
il.Emit(OpCodes.Stloc_1); // [string]
}
if (prop.PropertyType.FullName == LinqBinary)
{
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]
}
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
if (prop.PropertyType == typeof(string))
{
var endOfSize = il.DefineLabel();
// don't set if 0
il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size]
il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter]
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size]
il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter]
il.MarkLabel(endOfSize);
}
if (checkForDuplicates)
{
// stack is now [parameters] [parameter]
il.Emit(OpCodes.Pop); // don't need parameter any more
}
else
{
// stack is now [parameters] [parameters] [parameter]
// blindly add
il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
}
}
// stack is currently [parameters]
il.Emit(OpCodes.Pop); // stack is now empty
if (literals.Count != 0 && propsArr != null)
{
il.Emit(OpCodes.Ldarg_0); // command
il.Emit(OpCodes.Ldarg_0); // command, command
var cmdText = typeof(IDbCommand).GetProperty("CommandText");
il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql
Dictionary<Type, LocalBuilder> locals = null;
LocalBuilder local = null;
foreach (var literal in literals)
{
// find the best member, preferring case-sensitive
PropertyInfo exact = null, fallback = null;
string huntName = literal.Member;
for (int i = 0; i < propsArr.Length; i++)
{
string thisName = propsArr[i].Name;
if (string.Equals(thisName, huntName, StringComparison.InvariantCultureIgnoreCase))
{
fallback = propsArr[i];
if (string.Equals(thisName, huntName, StringComparison.InvariantCulture))
{
exact = fallback;
break;
}
}
}
var prop = exact ?? fallback;
if (prop != null)
{
il.Emit(OpCodes.Ldstr, literal.Token);
il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // command, sql, typed value
Type propType = prop.PropertyType;
var typeCode = Type.GetTypeCode(propType);
switch (typeCode)
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.UInt32:
case TypeCode.Int32:
case TypeCode.UInt64:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
// neeed to stloc, ldloca, call
// re-use existing locals (both the last known, and via a dictionary)
var convert = GetToString(typeCode);
if (local == null || local.LocalType != propType)
{
if (locals == null)
{
locals = new Dictionary<Type, LocalBuilder>();
local = null;
}
else
{
if (!locals.TryGetValue(propType, out local)) local = null;
}
if (local == null)
{
local = il.DeclareLocal(propType);
locals.Add(propType, local);
}
}
il.Emit(OpCodes.Stloc, local); // command, sql
il.Emit(OpCodes.Ldloca, local); // command, sql, ref-to-value
il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, ref-to-value, culture
il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value
break;
default:
if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value
il.EmitCall(OpCodes.Call, format, null); // command, sql, string value
break;
}
il.EmitCall(OpCodes.Callvirt, StringReplace, null);
}
}
il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod(), null); // empty
}
il.Emit(OpCodes.Ret);
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
}
static readonly Dictionary<TypeCode, MethodInfo> toStrings = new[]
{
typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short),
typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal)
}.ToDictionary(x => Type.GetTypeCode(x), x => x.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(IFormatProvider) }, null));
static MethodInfo GetToString(TypeCode typeCode)
{
MethodInfo method;
return toStrings.TryGetValue(typeCode, out method) ? method : null;
}
static readonly MethodInfo StringReplace = typeof(string).GetMethod("Replace", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(string) }, null),
InvariantCulture = typeof(CultureInfo).GetProperty("InvariantCulture", BindingFlags.Public | BindingFlags.Static).GetGetMethod();
private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
{
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
return cmd.ExecuteNonQuery();
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
}
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
var reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
wasClosed = false;
return reader;
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
}
private static Action<IDbCommand, object> GetParameterReader(IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = (object)param as IEnumerable;
Identity identity;
CacheInfo info = null;
if (multiExec != null && !(multiExec is string))
{
throw new NotSupportedException("MultiExec is not supported by ExecuteReader");
}
// nice and simple
if (param != null)
{
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param);
}
var paramReader = info == null ? null : info.ParamReader;
return paramReader;
}
private static Func<IDataReader, object> GetStructDeserializer(Type type, Type effectiveType, 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 (type == typeof(char))
{ // this *does* need special handling, though
return r => SqlMapper.ReadChar(r.GetValue(index));
}
if (type == typeof(char?))
{
return r => SqlMapper.ReadNullableChar(r.GetValue(index));
}
if (type.FullName == LinqBinary)
{
return r => Activator.CreateInstance(type, r.GetValue(index));
}
#pragma warning restore 618
if (effectiveType.IsEnum)
{ // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum
return r =>
{
var val = r.GetValue(index);
return val is DBNull ? null : Enum.ToObject(effectiveType, val);
};
}
return r =>
{
var val = r.GetValue(index);
return val is DBNull ? null : val;
};
}
static readonly MethodInfo
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
.Select(p => p.GetGetMethod()).First();
/// <summary>
/// Gets type-map for the given type
/// </summary>
/// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns>
public static ITypeMap GetTypeMap(Type type)
{
if (type == null) throw new ArgumentNullException("type");
var map = (ITypeMap)_typeMaps[type];
if (map == null)
{
lock (_typeMaps)
{ // double-checked; store this to avoid reflection next time we see this type
// since multiple queries commonly use the same domain-entity/DTO/view-model type
map = (ITypeMap)_typeMaps[type];
if (map == null)
{
map = new DefaultTypeMap(type);
_typeMaps[type] = map;
}
}
}
return map;
}
// use Hashtable to get free lockless reading
private static readonly Hashtable _typeMaps = new Hashtable();
/// <summary>
/// Set custom mapping for type deserializers
/// </summary>
/// <param name="type">Entity type to override</param>
/// <param name="map">Mapping rules impementation, null to remove custom map</param>
public static void SetTypeMap(Type type, ITypeMap map)
{
if (type == null)
throw new ArgumentNullException("type");
if (map == null || map is DefaultTypeMap)
{
lock (_typeMaps)
{
_typeMaps.Remove(type);
}
}
else
{
lock (_typeMaps)
{
_typeMaps[type] = map;
}
}
PurgeQueryCacheByType(type);
}
/// <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
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#else
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(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int));
il.DeclareLocal(type);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
if (length == -1)
{
length = reader.FieldCount - startBound;
}
if (reader.FieldCount <= startBound)
{
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
}
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
ITypeMap typeMap = GetTypeMap(type);
int index = startBound;
ConstructorInfo specializedConstructor = null;
bool supportInitialize = false;
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);
il.Emit(OpCodes.Initobj, type);
}
else
{
var types = new Type[length];
for (int i = startBound; i < startBound + length; i++)
{
types[i - startBound] = reader.GetFieldType(i);
}
var ctor = typeMap.FindConstructor(names, types);
if (ctor == null)
{
string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
}
if (ctor.GetParameters().Length == 0)
{
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_1);
supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
}
}
else
specializedConstructor = ctor;
}
il.BeginExceptionBlock();
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
}
else if (specializedConstructor == null)
{
il.Emit(OpCodes.Ldloc_1);// [target]
}
var members = (specializedConstructor != null
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
: names.Select(n => typeMap.GetMember(n))).ToList();
// stack is now [target]
bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -1;
foreach (var item in members)
{
if (item != null)
{
if (specializedConstructor == null)
il.Emit(OpCodes.Dup); // stack is now [target][target]
Label isDbNullLabel = il.DefineLabel();
Label finishLabel = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
EmitInt32(il, index); // stack is now [target][target][reader][index]
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
Type memberType = item.MemberType;
if (memberType == typeof(char) || memberType == typeof(char?))
{
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
// unbox nullable enums as the primitive, i.e. byte etc
var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
if (unboxType.IsEnum)
{
if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.MarkLabel(isNotString);
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value]
}
}
else if (memberType.FullName == LinqBinary)
{
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
}
else
{
Type dataType = reader.GetFieldType(index);
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
if (dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
// not a direct match; need to tweak the unbox
MethodInfo op;
if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)
{ // this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
}
else
{
bool handled = true;
OpCode opCode = default(OpCode);
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but
// this feels an edge-case
handled = false;
}
else
{
switch (unboxTypeCode)
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
}
if (handled)
{ // unbox as the data-type, then use IL-level convert
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (unboxTypeCode == TypeCode.Boolean)
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{ // use flexible conversion
il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]
}
}
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
}
}
if (specializedConstructor == null)
{
// Store the value in the property/field
if (item.Property != null)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
else
{
il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
}
}
il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
if (specializedConstructor != null)
{
il.Emit(OpCodes.Pop);
if (item.MemberType.IsValueType)
{
int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
LoadLocalAddress(il, localIndex);
il.Emit(OpCodes.Initobj, item.MemberType);
LoadLocal(il, localIndex);
}
else
{
il.Emit(OpCodes.Ldnull);
}
}
else
{
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target]
}
if (first && returnNullIfFirstMissing)
{
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull); // stack is now [null]
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br, allDone);
}
il.MarkLabel(finishLabel);
}
first = false;
index += 1;
}
if (type.IsValueType)
{
il.Emit(OpCodes.Pop);
}
else
{
if (specializedConstructor != null)
{
il.Emit(OpCodes.Newobj, specializedConstructor);
}
il.Emit(OpCodes.Stloc_1); // stack is empty
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("EndInit"), null);
}
}
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.EndExceptionBlock();
il.Emit(OpCodes.Ldloc_1); // stack is [rval]
if (type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
il.Emit(OpCodes.Ret);
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
}
static MethodInfo GetOperator(Type from, Type to)
{
if (to == null) return null;
MethodInfo[] fromMethods, toMethods;
return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
?? ResolveOperator(fromMethods, from, to, "op_Explicit")
?? ResolveOperator(toMethods, from, to, "op_Explicit");
}
static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name)
{
for (int i = 0; i < methods.Length; i++)
{
if (methods[i].Name != name || methods[i].ReturnType != to) continue;
var args = methods[i].GetParameters();
if (args.Length != 1 || args[0].ParameterType != from) continue;
return methods[i];
}
return null;
}
private static void LoadLocal(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch (index)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Ldloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloc, (short)index);
}
break;
}
}
private static void StoreLocal(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch (index)
{
case 0: il.Emit(OpCodes.Stloc_0); break;
case 1: il.Emit(OpCodes.Stloc_1); break;
case 2: il.Emit(OpCodes.Stloc_2); break;
case 3: il.Emit(OpCodes.Stloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Stloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Stloc, (short)index);
}
break;
}
}
private static void LoadLocalAddress(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
if (index <= 255)
{
il.Emit(OpCodes.Ldloca_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloca, (short)index);
}
}
/// <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)
{
Exception toThrow;
try
{
string name = "(n/a)", value = "(n/a)";
if (reader != null && index >= 0 && index < reader.FieldCount)
{
name = reader.GetName(index);
try
{
object val = reader.GetValue(index); // if there throw an exception, then I got one message, but Which column?
if (val == null || val is DBNull)
{
value = "<null>";
}
else
{
value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
}
}
catch (Exception valEx)
{
value = valEx.Message;
}
}
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
}
catch
{ // throw the **original** exception, wrapped as DataException
toThrow = new DataException(ex.Message, ex);
}
throw toThrow;
}
private static void EmitInt32(ILGenerator il, int value)
{
switch (value)
{
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
default:
if (value >= -128 && value <= 127)
{
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
break;
}
}
/// <summary>
/// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.
/// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical
/// schema to share startegies. Note that usual equivalence rules apply: any equivalent connection strings
/// <b>MUST</b> yield the same hash-code.
/// </summary>
public static IEqualityComparer<string> ConnectionStringComparer
{
get { return connectionStringComparer; }
set { connectionStringComparer = value ?? StringComparer.Ordinal; }
}
private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal;
/// <summary>
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query
/// </summary>
public partial class GridReader : IDisposable
{
private IDataReader reader;
private IDbCommand command;
private Identity identity;
internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
{
this.command = command;
this.reader = reader;
this.identity = identity;
}
#if !CSHARP30
/// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
public IEnumerable<dynamic> Read(bool buffered = true)
{
return Read<DapperRow>(buffered);
}
#endif
#if CSHARP30
/// <summary>
/// Read the next grid of results
/// </summary>
public IEnumerable<T> Read<T>()
{
return Read<T>(true);
}
#endif
/// <summary>
/// Read the next grid of results
/// </summary>
#if CSHARP30
public IEnumerable<T> Read<T>(bool buffered)
#else
public IEnumerable<T> Read<T>(bool buffered = true)
#endif
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
cache.Deserializer = deserializer;
}
consumed = true;
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
return buffered ? result.ToList() : result;
}
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst),
typeof(TSecond),
typeof(TThird),
typeof(TFourth),
typeof(TFifth),
typeof(TSixth),
typeof(TSeventh)
}, gridIndex);
try
{
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity))
{
yield return r;
}
}
finally
{
NextResult();
}
}
#if CSHARP30
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TReturn>(func, splitOn, true);
}
#endif
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#if CSHARP30
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TThird, TReturn>(func, splitOn, true);
}
#endif
/// <summary>
/// Read multiple objects from a single recordset on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#if CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true);
}
#endif
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#if !CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#endif
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{
try
{
while (index == gridIndex && reader.Read())
{
yield return (T)deserializer(reader);
}
}
finally // finally so that First etc progresses things even when multiple rows
{
if (index == gridIndex)
{
NextResult();
}
}
}
private int gridIndex, readCount;
private bool consumed;
private void NextResult()
{
if (reader.NextResult())
{
readCount++;
gridIndex++;
consumed = false;
}
else
{
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
Dispose();
}
}
/// <summary>
/// Dispose the grid, closing and disposing both the underlying reader and command.
/// </summary>
public void Dispose()
{
if (reader != null)
{
if (!reader.IsClosed && command != null) command.Cancel();
reader.Dispose();
reader = null;
}
if (command != null)
{
command.Dispose();
command = null;
}
}
}
/// <summary>
/// Used to pass a DataTable as a TableValuedParameter
/// </summary>
public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName
#if !CSHARP30
= null
#endif
)
{
return new TableValuedParameter(table, typeName);
}
}
/// <summary>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary>
partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup
{
internal const DbType EnumerableMultiParameter = (DbType)(-1);
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;
object SqlMapper.IParameterLookup.this[string member]
{
get
{
ParamInfo param;
return parameters.TryGetValue(member, out param) ? param.Value : null;
}
}
partial class ParamInfo
{
public string Name { get; set; }
public object Value { get; set; }
public ParameterDirection ParameterDirection { get; set; }
public DbType? DbType { get; set; }
public int? Size { get; set; }
public IDbDataParameter AttachedParam { get; set; }
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public DynamicParameters()
{
RemoveUnused = true;
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public DynamicParameters(object template)
{
RemoveUnused = true;
AddDynamicParams(template);
}
/// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddDynamicParams(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
)
{
var obj = param as object;
if (obj != null)
{
var subDynamic = obj as DynamicParameters;
if (subDynamic == null)
{
var dictionary = obj as IEnumerable<KeyValuePair<string, object>>;
if (dictionary == null)
{
templates = templates ?? new List<object>();
templates.Add(obj);
}
else
{
foreach (var kvp in dictionary)
{
#if CSHARP30
Add(kvp.Key, kvp.Value, null, null, null);
#else
Add(kvp.Key, kvp.Value);
#endif
}
}
}
else
{
if (subDynamic.parameters != null)
{
foreach (var kvp in subDynamic.parameters)
{
parameters.Add(kvp.Key, kvp.Value);
}
}
if (subDynamic.templates != null)
{
templates = templates ?? new List<object>();
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
#else
string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
#endif
)
{
parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
}
static string Clean(string name)
{
if (!string.IsNullOrEmpty(name))
{
switch (name[0])
{
case '@':
case ':':
case '?':
return name.Substring(1);
}
}
return name;
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
}
/// <summary>
/// If true, the command-text is inspected and only values that are clearly used are included on the connection
/// </summary>
public bool RemoveUnused { get; set; }
/// <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>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
var literals = SqlMapper.GetLiteralTokens(identity.sql);
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, true, RemoveUnused, literals);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
}
foreach (var param in parameters.Values)
{
var dbType = param.DbType;
var val = param.Value;
string name = Clean(param.Name);
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter;
if (dbType == null && val != null && !isCustomQueryParameter) dbType = SqlMapper.LookupDbType(val.GetType(), name);
if (dbType == DynamicParameters.EnumerableMultiParameter)
{
#pragma warning disable 612, 618
SqlMapper.PackListParameters(command, name, val);
#pragma warning restore 612, 618
}
else if (isCustomQueryParameter)
{
((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name);
}
else
{
bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if (add)
{
p = command.CreateParameter();
p.ParameterName = name;
}
else
{
p = (IDbDataParameter)command.Parameters[name];
}
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
var s = val as string;
if (s != null)
{
if (s.Length <= 4000)
{
p.Size = 4000;
}
}
if (param.Size != null)
{
p.Size = param.Size.Value;
}
if (dbType != null && p.DbType != dbType)
{
p.DbType = dbType.Value;
}
if (add)
{
command.Parameters.Add(p);
}
param.AttachedParam = p;
}
}
// note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
if (literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals);
}
/// <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;
if (val == DBNull.Value)
{
if (default(T) != null)
{
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
}
return default(T);
}
return (T)val;
}
}
/// <summary>
/// Used to pass a DataTable as a TableValuedParameter
/// </summary>
sealed partial class TableValuedParameter : Dapper.SqlMapper.ICustomQueryParameter
{
private readonly DataTable table;
private readonly string typeName;
/// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table) : this(table, null) { }
/// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table, string typeName)
{
this.table = table;
this.typeName = typeName;
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{
var param = command.CreateParameter();
param.ParameterName = name;
param.Value = (object)table ?? DBNull.Value;
if (!string.IsNullOrEmpty(typeName))
{
var sqlParam = param as System.Data.SqlClient.SqlParameter;
if (sqlParam != null)
{
sqlParam.TypeName = typeName;
sqlParam.SqlDbType = SqlDbType.Structured;
}
}
command.Parameters.Add(param);
}
}
/// <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>
sealed partial class DbString : Dapper.SqlMapper.ICustomQueryParameter
{
/// <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)
{
throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified");
}
var param = command.CreateParameter();
param.ParameterName = name;
param.Value = (object)Value ?? DBNull.Value;
if (Length == -1 && Value != null && Value.Length <= 4000)
{
param.Size = 4000;
}
else
{
param.Size = Length;
}
param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
command.Parameters.Add(param);
}
}
/// <summary>
/// Handles variances in features per DBMS
/// </summary>
partial class FeatureSupport
{
/// <summary>
/// Dictionary of supported features index by connection type name
/// </summary>
private static readonly Dictionary<string, FeatureSupport> FeatureList = new Dictionary<string, FeatureSupport>(StringComparer.InvariantCultureIgnoreCase) {
{"sqlserverconnection", new FeatureSupport { Arrays = false}},
{"npgsqlconnection", new FeatureSupport {Arrays = true}}
};
/// <summary>
/// Gets the featureset based on the passed connection
/// </summary>
public static FeatureSupport Get(IDbConnection connection)
{
string name = connection.GetType().Name;
FeatureSupport features;
return FeatureList.TryGetValue(name, out features) ? features : FeatureList.Values.First();
}
/// <summary>
/// True if the db supports array columns e.g. Postgresql
/// </summary>
public bool Arrays { get; set; }
}
/// <summary>
/// Represents simple memeber map for one of target parameter or property or field to source DataReader column
/// </summary>
sealed partial class SimpleMemberMap : SqlMapper.IMemberMap
{
private readonly string _columnName;
private readonly PropertyInfo _property;
private readonly FieldInfo _field;
private readonly ParameterInfo _parameter;
/// <summary>
/// Creates instance for simple property mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="property">Target property</param>
public SimpleMemberMap(string columnName, PropertyInfo property)
{
if (columnName == null)
throw new ArgumentNullException("columnName");
if (property == null)
throw new ArgumentNullException("property");
_columnName = columnName;
_property = property;
}
/// <summary>
/// Creates instance for simple field mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="field">Target property</param>
public SimpleMemberMap(string columnName, FieldInfo field)
{
if (columnName == null)
throw new ArgumentNullException("columnName");
if (field == null)
throw new ArgumentNullException("field");
_columnName = columnName;
_field = field;
}
/// <summary>
/// Creates instance for simple constructor parameter mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="parameter">Target constructor parameter</param>
public SimpleMemberMap(string columnName, ParameterInfo parameter)
{
if (columnName == null)
throw new ArgumentNullException("columnName");
if (parameter == null)
throw new ArgumentNullException("parameter");
_columnName = columnName;
_parameter = parameter;
}
/// <summary>
/// DataReader column name
/// </summary>
public string ColumnName
{
get { return _columnName; }
}
/// <summary>
/// Target member type
/// </summary>
public Type MemberType
{
get
{
if (_field != null)
return _field.FieldType;
if (_property != null)
return _property.PropertyType;
if (_parameter != null)
return _parameter.ParameterType;
return null;
}
}
/// <summary>
/// Target property
/// </summary>
public PropertyInfo Property
{
get { return _property; }
}
/// <summary>
/// Target field
/// </summary>
public FieldInfo Field
{
get { return _field; }
}
/// <summary>
/// Target constructor parameter
/// </summary>
public ParameterInfo Parameter
{
get { return _parameter; }
}
}
/// <summary>
/// Represents default type mapping strategy used by Dapper
/// </summary>
sealed partial class DefaultTypeMap : SqlMapper.ITypeMap
{
private readonly List<FieldInfo> _fields;
private readonly List<PropertyInfo> _properties;
private readonly Type _type;
/// <summary>
/// Creates default type map
/// </summary>
/// <param name="type">Entity type</param>
public DefaultTypeMap(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
_fields = GetSettableFields(type);
_properties = GetSettableProps(type);
_type = type;
}
internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type)
{
return propertyInfo.DeclaringType == type ?
propertyInfo.GetSetMethod(true) :
propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true);
}
internal static List<PropertyInfo> GetSettableProps(Type t)
{
return t
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(p => GetPropertySetter(p, t) != null)
.ToList();
}
internal static List<FieldInfo> GetSettableFields(Type t)
{
return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
}
/// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
{
ParameterInfo[] ctorParameters = ctor.GetParameters();
if (ctorParameters.Length == 0)
return ctor;
if (ctorParameters.Length != types.Length)
continue;
int i = 0;
for (; i < ctorParameters.Length; i++)
{
if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
break;
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary)
continue;
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
if (unboxedType != types[i]
&& !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
&& !(unboxedType == typeof(char) && types[i] == typeof(string)))
break;
}
if (i == ctorParameters.Length)
return ctor;
}
return null;
}
/// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
var parameters = constructor.GetParameters();
return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetMember(string columnName)
{
var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
if (property != null)
return new SimpleMemberMap(columnName, property);
var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
if (field != null)
return new SimpleMemberMap(columnName, field);
return null;
}
}
/// <summary>
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
/// </summary>
sealed partial class CustomPropertyTypeMap : SqlMapper.ITypeMap
{
private readonly Type _type;
private readonly Func<Type, string, PropertyInfo> _propertySelector;
/// <summary>
/// Creates custom property mapping
/// </summary>
/// <param name="type">Target entity type</param>
/// <param name="propertySelector">Property selector based on target type and DataReader column name</param>
public CustomPropertyTypeMap(Type type, Func<Type, string, PropertyInfo> propertySelector)
{
if (type == null)
throw new ArgumentNullException("type");
if (propertySelector == null)
throw new ArgumentNullException("propertySelector");
_type = type;
_propertySelector = propertySelector;
}
/// <summary>
/// Always returns default constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Default constructor</returns>
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _type.GetConstructor(new Type[0]);
}
/// <summary>
/// Not impelmeneted as far as default constructor used for all cases
/// </summary>
/// <param name="constructor"></param>
/// <param name="columnName"></param>
/// <returns></returns>
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
throw new NotSupportedException();
}
/// <summary>
/// Returns property based on selector strategy
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Poperty member map</returns>
public SqlMapper.IMemberMap GetMember(string columnName)
{
var prop = _propertySelector(_type, columnName);
return prop != null ? new SimpleMemberMap(columnName, prop) : null;
}
}
// Define DAPPER_MAKE_PRIVATE if you reference Dapper by source
// and you like to make the Dapper types private (in order to avoid
// conflicts with other projects that also reference Dapper by source)
#if !DAPPER_MAKE_PRIVATE
public partial class SqlMapper
{
}
public partial class DynamicParameters
{
}
public partial class DbString
{
}
public partial class SimpleMemberMap
{
}
public partial class DefaultTypeMap
{
}
public partial class CustomPropertyTypeMap
{
}
public partial class FeatureSupport
{
}
#endif
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C#
1
https://gitee.com/poisson/Dapper.git
[email protected]:poisson/Dapper.git
poisson
Dapper
Dapper
master

搜索帮助