Commit ccc189c2 authored by James Holwell's avatar James Holwell

Merge remote-tracking branch 'upstream/master' into arbitrary-multi-maps

Conflicts:
	Dapper NET45/SqlMapperAsync.cs
parents 3b0fd2d0 5472d2ab
......@@ -32,5 +32,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -276,6 +276,36 @@ public interface ITypeHandler
object Parse(Type destinationType, object value);
}
/// <summary>
/// A type handler for data-types that are supported by the underlying provider, but which need
/// a well-known UdtTypeName to be specified
/// </summary>
public class UdtTypeHandler : ITypeHandler
{
private readonly string udtTypeName;
/// <summary>
/// Creates a new instance of UdtTypeHandler with the specified UdtTypeName
/// </summary>
public UdtTypeHandler(string udtTypeName)
{
if (string.IsNullOrEmpty(udtTypeName)) throw new ArgumentException("Cannot be null or empty", udtTypeName);
this.udtTypeName = udtTypeName;
}
object ITypeHandler.Parse(Type destinationType, object value)
{
return value is DBNull ? null : value;
}
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
parameter.Value = ((object)value) ?? DBNull.Value;
if (parameter is System.Data.SqlClient.SqlParameter)
{
((System.Data.SqlClient.SqlParameter)parameter).UdtTypeName = udtTypeName;
}
}
}
/// <summary>
/// Base-class for simple type-handlers
/// </summary>
......@@ -809,10 +839,21 @@ internal static DbType LookupDbType(Type type, string name, out ITypeHandler han
{
return DbType.Object;
}
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
switch (type.FullName)
{
case "Microsoft.SqlServer.Types.SqlGeography":
AddTypeHandler(type, handler = new UdtTypeHandler("GEOGRAPHY"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlGeometry":
AddTypeHandler(type, handler = new UdtTypeHandler("GEOMETRY"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlHierarchyId":
AddTypeHandler(type, handler = new UdtTypeHandler("HIERARCHYID"));
return DbType.Object;
}
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>
......@@ -1231,7 +1272,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteReaderImpl(cnn, ref command);
return ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default);
}
/// <summary>
......@@ -1244,7 +1285,19 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
/// </remarks>
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteReaderImpl(cnn, ref command);
return ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default);
}
/// <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, CommandBehavior commandBehavior)
{
return ExecuteReaderImpl(cnn, ref command, commandBehavior);
}
#if !CSHARP30
......@@ -1385,7 +1438,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
{
if (wasClosed) cnn.Open();
cmd = command.SetupCommand(cnn, info.ParamReader);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
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
......@@ -1423,7 +1476,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
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
......@@ -1679,7 +1732,7 @@ partial class DontMap { }
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader();
ownedReader = ownedCommand.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
......@@ -2486,96 +2539,92 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
// 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)
{
if (FeatureSupport.Get(command.Connection).Arrays)
{
var arrayParm = command.CreateParameter();
arrayParm.Value = list;
arrayParm.ParameterName = namePrefix;
command.Parameters.Add(arrayParm);
}
else
var arrayParm = command.CreateParameter();
arrayParm.Value = value ?? DBNull.Value;
arrayParm.ParameterName = namePrefix;
command.Parameters.Add(arrayParm);
}
else
{
var list = value as IEnumerable;
var count = 0;
bool isString = value is IEnumerable<string>;
bool isDbString = value is IEnumerable<DbString>;
foreach (var item in list)
{
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)
{
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 = 4000;
if (item != null && ((string)item).Length > 4000)
{
listParam.Size = -1;
}
listParam.Size = -1;
}
if (isDbString && item as DbString != null)
}
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)
{
var str = item as DbString;
str.AddParameter(command, listParam.ParameterName);
// looks like an optimize hint; leave it alone!
return match.Value;
}
else
{
command.Parameters.Add(listParam);
return "(SELECT " + variableName + " WHERE 1 = 0)";
}
}
var regexIncludingUnknown = @"([?@:]" + Regex.Escape(namePrefix) + @")(\s+(?i)unknown(?-i))?";
if (count == 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 =>
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// looks like an optimize hint; leave it alone!
return match.Value;
}
else
// 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++)
{
return "(SELECT " + variableName + " WHERE 1 = 0)";
sb.Append(',').Append(variableName).Append(i).Append(suffix);
}
});
var dummyParam = command.CreateParameter();
dummyParam.ParameterName = namePrefix;
dummyParam.Value = DBNull.Value;
command.Parameters.Add(dummyParam);
}
else
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
return sb.ToString();
}
else
{
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++)
{
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();
sb.Append(',').Append(variableName).Append(i);
}
});
}
return sb.Append(')').ToString();
}
});
}
}
......@@ -3112,7 +3161,7 @@ private static T ExecuteScalarImpl<T>(IDbConnection cnn, ref CommandDefinition c
return Parse<T>(result);
}
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command)
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
......@@ -3122,8 +3171,9 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
var reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
wasClosed = false;
if (wasClosed) commandBehavior |= CommandBehavior.CloseConnection;
var reader = cmd.ExecuteReader(commandBehavior);
wasClosed = false; // don't dispose before giving it to them!
return reader;
}
finally
......@@ -3380,7 +3430,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -1;
int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
foreach (var item in members)
{
if (item != null)
......@@ -3395,6 +3445,8 @@ public static void SetTypeMap(Type type, ITypeMap map)
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]
il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
StoreLocal(il, valueCopyLocal);
Type colType = reader.GetFieldType(index);
Type memberType = item.MemberType;
......@@ -3427,7 +3479,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
StoreLocal(il, enumDeclareLocal); // 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]
LoadLocal(il, enumDeclareLocal); // 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.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
......@@ -3555,6 +3607,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
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
LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
il.EndExceptionBlock();
......@@ -3730,36 +3783,33 @@ private static void LoadLocalAddress(ILGenerator il, int 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)
[Obsolete("Intended for internal use only")]
public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value)
{
Exception toThrow;
try
{
string name = "(n/a)", value = "(n/a)";
string name = "(n/a)", formattedValue = "(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)
if (value == null || value is DBNull)
{
value = "<null>";
formattedValue = "<null>";
}
else
{
value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
formattedValue = Convert.ToString(value) + " - " + Type.GetTypeCode(value.GetType());
}
}
catch (Exception valEx)
{
value = valEx.Message;
formattedValue = valEx.Message;
}
}
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, formattedValue), ex);
}
catch
{ // throw the **original** exception, wrapped as DataException
......@@ -3837,7 +3887,7 @@ internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
/// </summary>
public IEnumerable<dynamic> Read(bool buffered = true)
{
return Read<DapperRow>(buffered);
return ReadImpl<dynamic>(typeof(DapperRow), buffered);
}
#endif
......@@ -3859,21 +3909,7 @@ public IEnumerable<T> Read<T>(bool buffered)
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, true);
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;
return ReadImpl<T>(typeof(T), buffered);
}
/// <summary>
......@@ -3886,6 +3922,11 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
#endif
{
if (type == null) throw new ArgumentNullException("type");
return ReadImpl<object>(type, buffered);
}
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
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(type, gridIndex);
......@@ -3899,10 +3940,11 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
cache.Deserializer = deserializer;
}
consumed = true;
var result = ReadDeferred<object>(gridIndex, deserializer.Func, typedIdentity);
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[] {
......@@ -4069,7 +4111,6 @@ private void NextResult()
Dispose();
}
}
/// <summary>
/// Dispose the grid, closing and disposing both the underlying reader and command.
......@@ -4535,28 +4576,27 @@ public void AddParameter(IDbCommand command, string name)
/// </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}}
};
private static readonly FeatureSupport
@default = new FeatureSupport(false),
postgres = new FeatureSupport(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();
string name = connection == null ? null : connection.GetType().Name;
if (string.Equals(name, "npgsqlconnection", StringComparison.InvariantCultureIgnoreCase)) return postgres;
return @default;
}
private FeatureSupport(bool arrays)
{
Arrays = arrays;
}
/// <summary>
/// True if the db supports array columns e.g. Postgresql
/// </summary>
public bool Arrays { get; set; }
public bool Arrays { get; private set; }
}
/// <summary>
......@@ -4782,25 +4822,39 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor,
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))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
if (property == null && MatchNamesWithUnderscores)
{
property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), 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))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
if (field == null && MatchNamesWithUnderscores)
{
field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
}
if (field != null)
return new SimpleMemberMap(columnName, field);
return null;
}
/// <summary>
/// Should column names like User_Id be allowed to match properties/fields like UserId ?
/// </summary>
public static bool MatchNamesWithUnderscores { get; set; }
}
/// <summary>
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
/// </summary>
......
......@@ -13,6 +13,23 @@ namespace Dapper
public static partial class SqlMapper
{
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryAsync<dynamic>(cnn, typeof(DapperRow), new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryAsync<dynamic>(cnn, typeof(DapperRow), command);
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
......@@ -52,19 +69,49 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
DbDataReader reader = null;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false))
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, cancel).ConfigureAwait(false);
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if (command.AddToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
if (command.Buffered)
{
List<T> buffer = new List<T>();
while (await reader.ReadAsync(cancel).ConfigureAwait(false))
{
buffer.Add((T)func(reader));
}
return buffer;
}
else
{
return ExecuteReader<T>(reader, effectiveType, identity, info, command.AddToCache).ToList();
// can't use ReadAsync / cancellation; but this will have to do
wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
var deferred = ExecuteReaderSync<T>(reader, func);
reader = null; // to prevent it being disposed before the caller gets to see it
return deferred;
}
}
finally
{
using (reader) { } // dispose if non-null
if (wasClosed) cnn.Close();
}
}
}
......@@ -111,7 +158,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
CacheInfo info = null;
string masterSql = null;
......@@ -205,7 +252,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
{
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
return await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false);
}
finally
......@@ -394,10 +441,11 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false))
using (var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false))
{
if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), map, splitOn, reader, identity);
return command.Buffered ? results.ToList() : results;
}
......@@ -451,19 +499,9 @@ private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbC
if (wasClosed) cnn.Close();
}
}
private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Type effectiveType, Identity identity, CacheInfo info, bool addToCache)
private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader,object> func)
{
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if(addToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
while (reader.Read())
{
yield return (T)func(reader);
......@@ -484,6 +522,111 @@ private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Type effectiv
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return QueryMultipleAsync(cnn, command);
}
partial class GridReader
{
CancellationToken cancel;
internal GridReader(IDbCommand command, IDataReader reader, Identity identity, CancellationToken cancel) : this(command, reader, identity)
{
this.cancel = cancel;
}
/// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
{
return ReadAsyncImpl<dynamic>(typeof(DapperRow), buffered);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
{
if (type == null) throw new ArgumentNullException("type");
return ReadAsyncImpl<object>(type, buffered);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
{
return ReadAsyncImpl<T>(typeof(T), buffered);
}
private async Task NextResultAsync()
{
if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false))
{
readCount++;
gridIndex++;
consumed = false;
}
else
{
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
Dispose();
}
}
private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
{
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(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, true);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
consumed = true;
if (buffered && this.reader is DbDataReader)
{
return ReadBufferedAsync<T>(gridIndex, deserializer.Func, typedIdentity);
}
else
{
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario
return Task.FromResult(result);
}
}
private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{
//try
//{
var reader = (DbDataReader)this.reader;
List<T> buffer = new List<T>();
while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false))
{
buffer.Add((T)deserializer(reader));
}
if (index == gridIndex) // need to do this outside of the finally pre-C#6
{
await NextResultAsync().ConfigureAwait(false);
}
return buffer;
//}
//finally // finally so that First etc progresses things even when multiple rows
//{
// if (index == gridIndex)
// {
// await NextResultAsync().ConfigureAwait(false);
// }
//}
}
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
......@@ -498,11 +641,11 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader);
reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default, command.CancellationToken).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false);
var result = new GridReader(cmd, reader, identity);
var result = new GridReader(cmd, reader, identity, command.CancellationToken);
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
......@@ -580,8 +723,8 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
try
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default, command.CancellationToken).ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false);
wasClosed = false;
return reader;
}
......@@ -658,7 +801,7 @@ private async static Task<T> ExecuteScalarImplAsync<T>(IDbConnection cnn, Comman
try
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false);
}
finally
......
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -57,10 +57,15 @@
<Compile Include="..\Dapper.EntityFramework NET45\DbGeographyHandler.cs">
<Link>DbGeographyHandler.cs</Link>
</Compile>
<Compile Include="..\Dapper.EntityFramework NET45\DbGeometryHandler.cs">
<Link>DbGeometryHandler.cs</Link>
</Compile>
<Compile Include="..\Dapper.EntityFramework NET45\Handlers.cs">
<Link>Handlers.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\Dapper.EntityFramework NET45\Properties\AssemblyInfo.cs">
<Link>AssemblyInfo.cs</Link>
</Compile>
<Compile Include="SqlServerTypes\Loader.cs" />
</ItemGroup>
<ItemGroup>
......@@ -92,6 +97,9 @@
<Name>Dapper NET40</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
......
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Dapper.EntityFramework NET40")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dapper.EntityFramework NET40")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("85331b38-d6e0-41f6-b1ed-b27a8747f827")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
......@@ -52,6 +52,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DbGeometryHandler.cs" />
<Compile Include="DbGeographyHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Handlers.cs" />
......
using Microsoft.SqlServer.Types;
using System;
using System.Data;
using System.Data.Entity.Spatial;
using System.Data.SqlClient;
namespace Dapper.EntityFramework
{
/// <summary>
/// Type-handler for the DbGeometry spatial type
/// </summary>
public class DbGeometryHandler : Dapper.SqlMapper.TypeHandler<DbGeometry>
{
/// <summary>
/// Create a new handler instance
/// </summary>
protected DbGeometryHandler() { }
/// <summary>
/// Default handler instance
/// </summary>
public static readonly DbGeometryHandler Default = new DbGeometryHandler();
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
public override void SetValue(IDbDataParameter parameter, DbGeometry value)
{
parameter.Value = value == null ? (object)DBNull.Value : (object)SqlGeometry.Parse(value.AsText());
if (parameter is SqlParameter)
{
((SqlParameter)parameter).UdtTypeName = "GEOMETRY";
}
}
/// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public override DbGeometry Parse(object value)
{
return (value == null || value is DBNull) ? null : DbGeometry.FromText(value.ToString());
}
}
}
......@@ -11,6 +11,7 @@ public static class Handlers
public static void Register()
{
SqlMapper.AddTypeHandler(DbGeographyHandler.Default);
SqlMapper.AddTypeHandler(DbGeometryHandler.Default);
}
}
}
......@@ -31,6 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
......@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata schemaVersion="2">
<id>Dapper.EntityFramework</id>
<version>1.26</version>
<version>1.33</version>
<title>Dapper entity framework type handlers</title>
<authors>Marc Gravell</authors>
<owners>Marc Gravell</owners>
......@@ -14,7 +14,7 @@
<tags>orm sql micro-orm</tags>
<dependencies>
<dependency id="EntityFramework" version="6.1.1" />
<dependency id="Dapper" version="1.26" />
<dependency id="Dapper" version="1.33" />
</dependencies>
<frameworkAssemblies>
<frameworkAssembly assemblyName="System.Core"/>
......
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -17,9 +17,17 @@ public void TestBasicStringUsage()
}
public void TestClassWithStringUsage()
{
var arr = connection.Query<BasicType>("select 'abc' as [Value], '123' as [Another_Value] union all select @txt, @txt2", new { txt = "def", txt2 = "456" }).ToArray();
arr.Select(x => x.Value).IsSequenceEqualTo(new[] { "abc", "def" });
arr.Select(x => x.AnotherValue).IsSequenceEqualTo(new[] { "123", "456" });
var oldMatch = Dapper.DefaultTypeMap.MatchNamesWithUnderscores;
try
{
DefaultTypeMap.MatchNamesWithUnderscores = true;
var arr = connection.Query<BasicType>("select 'abc' as [Value], '123' as [Another_Value] union all select @txt, @txt2", new { txt = "def", txt2 = "456" }).ToArray();
arr.Select(x => x.Value).IsSequenceEqualTo(new[] { "abc", "def" });
arr.Select(x => x.AnotherValue).IsSequenceEqualTo(new[] { "123", "456" });
} finally
{
DefaultTypeMap.MatchNamesWithUnderscores = oldMatch;
}
}
class BasicType
{
......
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
......@@ -4,6 +4,9 @@
using System.Data;
using System.Diagnostics;
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
namespace DapperTests_NET45
{
......@@ -18,6 +21,35 @@ public void TestBasicStringUsageAsync()
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
}
public void TestBasicStringUsageAsyncNonBuffered()
{
using (var connection = Program.GetOpenConnection())
{
var query = connection.QueryAsync<string>(new CommandDefinition("select 'abc' as [Value] union all select @txt", new { txt = "def" }, flags: CommandFlags.None));
var arr = query.Result.ToArray();
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
}
public void TestLongOperationWithCancellation()
{
using(var connection = Program.GetClosedConnection())
{
CancellationTokenSource cancel = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var task = connection.QueryAsync<int>(new CommandDefinition("waitfor delay '00:00:10';select 1", cancellationToken: cancel.Token));
try
{
if (!task.Wait(TimeSpan.FromSeconds(7)))
{
throw new TimeoutException(); // should have cancelled
}
}
catch (AggregateException agg)
{
(agg.InnerException is SqlException).IsTrue();
}
}
}
public void TestBasicStringUsageClosedAsync()
{
......@@ -29,6 +61,16 @@ public void TestBasicStringUsageClosedAsync()
}
}
public void TestQueryDynamicAsync()
{
using (var connection = Program.GetClosedConnection())
{
var row = connection.QueryAsync("select 'abc' as [Value]").Result.Single();
string value = row.Value;
value.IsEqualTo("abc");
}
}
public void TestClassWithStringUsageAsync()
{
using (var connection = Program.GetOpenConnection())
......@@ -122,8 +164,8 @@ public void TestMultiAsync()
{
using (Dapper.SqlMapper.GridReader multi = conn.QueryMultipleAsync("select 1; select 2").Result)
{
multi.Read<int>().Single().IsEqualTo(1);
multi.Read<int>().Single().IsEqualTo(2);
multi.ReadAsync<int>().Result.Single().IsEqualTo(1);
multi.ReadAsync<int>().Result.Single().IsEqualTo(2);
}
}
}
......@@ -133,8 +175,8 @@ public void TestMultiClosedConnAsync()
{
using (Dapper.SqlMapper.GridReader multi = conn.QueryMultipleAsync("select 1; select 2").Result)
{
multi.Read<int>().Single().IsEqualTo(1);
multi.Read<int>().Single().IsEqualTo(2);
multi.ReadAsync<int>().Result.Single().IsEqualTo(1);
multi.ReadAsync<int>().Result.Single().IsEqualTo(2);
}
}
}
......
......@@ -72,6 +72,10 @@
<Reference Include="LinFu.DynamicProxy">
<HintPath>NHibernate\LinFu.DynamicProxy.dll</HintPath>
</Reference>
<Reference Include="Microsoft.SqlServer.Types, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.SqlServer.Types.11.0.1\lib\net20\Microsoft.SqlServer.Types.dll</HintPath>
</Reference>
<Reference Include="Mono.Security">
<HintPath>..\packages\Npgsql.2.0.11\lib\Net40\Mono.Security.dll</HintPath>
</Reference>
......@@ -165,6 +169,7 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Include="SqlServerTypes\Loader.cs" />
<Compile Include="SubSonic\ActiveRecord.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
......@@ -228,6 +233,22 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Include="..\packages\Microsoft.SqlServer.Types.11.0.1\nativeBinaries\x64\msvcr100.dll">
<Link>SqlServerTypes\x64\msvcr100.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\packages\Microsoft.SqlServer.Types.11.0.1\nativeBinaries\x64\SqlServerSpatial110.dll">
<Link>SqlServerTypes\x64\SqlServerSpatial110.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\packages\Microsoft.SqlServer.Types.11.0.1\nativeBinaries\x86\msvcr100.dll">
<Link>SqlServerTypes\x86\msvcr100.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\packages\Microsoft.SqlServer.Types.11.0.1\nativeBinaries\x86\SqlServerSpatial110.dll">
<Link>SqlServerTypes\x86\SqlServerSpatial110.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="NHibernate\hibernate.cfg.xml" />
<Content Include="NHibernate\Post.hbm.xml" />
<Content Include="OrmLite\ServiceStack.Common.dll" />
......@@ -238,6 +259,7 @@
<Content Include="Soma\Soma.Core.dll" />
<Content Include="Soma\Soma.Core.IT.MsSql.dll" />
<Content Include="Soma\Soma.Core.IT.MsSqlCe.dll" />
<Content Include="SqlServerTypes\readme.htm" />
</ItemGroup>
<ItemGroup>
<Folder Include="bltoolkit\" />
......
......@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.28.0.0")]
[assembly: AssemblyVersion("1.34.0.0")]
[assembly: AssemblyFileVersion("1.34.0.0")]
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace SqlServerTypes
{
/// <summary>
/// Utility methods related to CLR Types for SQL Server
/// </summary>
internal class Utilities
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string libname);
/// <summary>
/// Loads the required native assemblies for the current architecture (x86 or x64)
/// </summary>
/// <param name="rootApplicationPath">
/// Root path of the current application. Use Server.MapPath(".") for ASP.NET applications
/// and AppDomain.CurrentDomain.BaseDirectory for desktop applications.
/// </param>
public static void LoadNativeAssemblies(string rootApplicationPath)
{
var nativeBinaryPath = IntPtr.Size > 4
? Path.Combine(rootApplicationPath, @"SqlServerTypes\x64\")
: Path.Combine(rootApplicationPath, @"SqlServerTypes\x86\");
LoadNativeAssembly(nativeBinaryPath, "msvcr100.dll");
LoadNativeAssembly(nativeBinaryPath, "SqlServerSpatial110.dll");
}
private static void LoadNativeAssembly(string nativeBinaryPath, string assemblyName)
{
var path = Path.Combine(nativeBinaryPath, assemblyName);
var ptr = LoadLibrary(path);
if (ptr == IntPtr.Zero)
{
throw new Exception(string.Format(
"Error loading {0} (ErrorCode: {1})",
assemblyName,
Marshal.GetLastWin32Error()));
}
}
}
}
\ No newline at end of file
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Microsoft.SqlServer.Types</title>
<style>
body {
background: #fff;
color: #505050;
margin: 20px;
}
#main {
background: #efefef;
padding: 5px 30px;
}
</style>
</head>
<body>
<div id="main">
<h1>Action required to load native assemblies</h1>
<p>
To deploy an application that uses spatial data types to a machine that does not have 'System CLR Types for SQL Server' installed you also need to deploy the native assembly SqlServerSpatial110.dll. Both x86 (32 bit) and x64 (64 bit) versions of this assembly have been added to your project under the SqlServerTypes\x86 and SqlServerTypes\x64 subdirectories. The native assembly msvcr100.dll is also included in case the C++ runtime is not installed.
</p>
<p>
You need to add code to load the correct one of these assemblies at runtime (depending on the current architecture).
</p>
<h2>ASP.NET applications</h2>
<p>
For ASP.NET applications, add the following line of code to the Application_Start method in Global.asax.cs:
<pre> SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~/bin"));</pre>
</p>
<h2>Desktop applications</h2>
<p>
For desktop applications, add the following line of code to run before any spatial operations are performed:
<pre> SqlServerTypes.Utilities.LoadNativeAssemblies(AppDomain.CurrentDomain.BaseDirectory);</pre>
</p>
</div>
</body>
</html>
\ No newline at end of file
......@@ -16,6 +16,8 @@
using System.Globalization;
using System.Threading;
using System.Data.Entity.Spatial;
using Microsoft.SqlServer.Types;
using System.Data.SqlTypes;
#if POSTGRESQL
using Npgsql;
#endif
......@@ -1390,10 +1392,10 @@ public void TestMultiMappingWithNonReturnedProperty()
2 as BlogId, 'Blog' as Title";
var postWithBlog = connection.Query<Post_DupeProp, Blog_DupeProp, Post_DupeProp>(sql,
(p, b) =>
{
p.Blog = b;
return p;
}, splitOn: "BlogId").First();
{
p.Blog = b;
return p;
}, splitOn: "BlogId").First();
postWithBlog.PostId.IsEqualTo(1);
postWithBlog.Title.IsEqualTo("Title");
......@@ -1406,7 +1408,7 @@ class Post_DupeProp
public int PostId { get; set; }
public string Title { get; set; }
public int BlogId { get; set; }
public Blog_DupeProp Blog { get; set; }
public Blog_DupeProp Blog { get; set; }
}
class Blog_DupeProp
......@@ -2676,7 +2678,7 @@ public void TestNullFromInt_NoRows()
public void TestChangingDefaultStringTypeMappingToAnsiString()
{
var sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType";
var param = new {testParam = "TestString"};
var param = new { testParam = "TestString" };
var result01 = connection.Query<string>(sql, param).FirstOrDefault();
result01.IsEqualTo("nvarchar");
......@@ -3011,7 +3013,7 @@ public void DataTableParameters()
{
connection.Query<int>("select count(1) from @ids", new { ids = table.AsTableValuedParameter() }).First();
throw new InvalidOperationException();
} catch(Exception ex)
} catch (Exception ex)
{
ex.Message.Equals("The table type parameter 'ids' must have a valid type name.");
}
......@@ -3020,7 +3022,7 @@ public void DataTableParameters()
public void DataTableParametersWithExtendedProperty()
{
try { connection.Execute("drop proc #DataTableParameters"); } catch { }
try { connection.Execute("drop table #DataTableParameters"); } catch { }
try { connection.Execute("drop table #DataTableParameters"); } catch { }
try { connection.Execute("drop type MyTVPType"); } catch { }
connection.Execute("create type MyTVPType as table (id int)");
connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids");
......@@ -3080,25 +3082,70 @@ public void GuidIn_SO_24177902()
class HazGeo
{
public int Id { get;set; }
public int Id { get; set; }
public DbGeography Geo { get; set; }
public DbGeometry Geometry { get; set; }
}
class HazSqlGeo
{
public int Id { get; set; }
public SqlGeography Geo { get; set; }
public SqlGeometry Geometry { get; set; }
}
public void DBGeography_SO24405645_SO24402424()
{
Dapper.EntityFramework.Handlers.Register();
connection.Execute("create table #Geo (id int, geo geography)");
connection.Execute("create table #Geo (id int, geo geography, geometry geometry)");
var obj = new HazGeo
{
Id = 1,
Geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326)
Geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326),
Geometry = DbGeometry.LineFromText("LINESTRING (100 100, 20 180, 180 180)", 0)
};
connection.Execute("insert #Geo(id, geo) values (@Id, @Geo)", obj);
connection.Execute("insert #Geo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj);
var row = connection.Query<HazGeo>("select * from #Geo where id=1").SingleOrDefault();
row.IsNotNull();
row.Id.IsEqualTo(1);
row.Geo.IsNotNull();
row.Geometry.IsNotNull();
}
public void SqlGeography_SO25538154()
{
Dapper.SqlMapper.ResetTypeHandlers();
connection.Execute("create table #SqlGeo (id int, geo geography, geometry geometry)");
var obj = new HazSqlGeo
{
Id = 1,
Geo = SqlGeography.STLineFromText(new SqlChars(new SqlString("LINESTRING(-122.360 47.656, -122.343 47.656 )")), 4326),
Geometry = SqlGeometry.STLineFromText(new SqlChars(new SqlString("LINESTRING (100 100, 20 180, 180 180)")), 0)
};
connection.Execute("insert #SqlGeo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj);
var row = connection.Query<HazSqlGeo>("select * from #SqlGeo where id=1").SingleOrDefault();
row.IsNotNull();
row.Id.IsEqualTo(1);
row.Geo.IsNotNull();
row.Geometry.IsNotNull();
}
public void SqlHierarchyId_SO18888911()
{
Dapper.SqlMapper.ResetTypeHandlers();
var row = connection.Query<HazSqlHierarchy>("select 3 as [Id], hierarchyid::Parse('/1/2/3/') as [Path]").Single();
row.Id.Equals(3);
row.Path.IsNotNull();
var val = connection.Query<SqlHierarchyId>("select @Path", row).Single();
val.IsNotNull();
}
public class HazSqlHierarchy
{
public int Id { get; set; }
public SqlHierarchyId Path { get; set; }
}
public void TypeBasedViaDynamic()
......@@ -3129,7 +3176,7 @@ public void TypeBasedViaTypeMulti()
Type type = GetSomeType();
dynamic first, second;
using(var multi = connection.QueryMultiple("select @A as [A], @B as [B]; select @C as [A], @D as [B]",
using (var multi = connection.QueryMultiple("select @A as [A], @B as [B]; select @C as [A], @D as [B]",
new { A = 123, B = "abc", C = 456, D = "def" }))
{
first = multi.Read(type).Single();
......@@ -3157,14 +3204,14 @@ static Type GetSomeType()
}
public class SomeType
{
public int A { get;set; }
public string B { get;set; }
public int A { get; set; }
public string B { get; set; }
}
class WithInit : ISupportInitialize
{
public string Value { get; set; }
public int Flags { get;set; }
public int Flags { get; set; }
void ISupportInitialize.BeginInit()
{
......@@ -3450,14 +3497,14 @@ public void TestBigIntForEverythingWorks_SqlLite()
}
private void TestBigIntForEverythingWorks_SqlLite_ByDataType<T>(string dbType)
{
using(var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")"))
using (var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")"))
{
reader.Read().IsTrue();
reader.GetFieldType(0).Equals(typeof(T));
reader.Read().IsFalse();
reader.NextResult().IsFalse();
}
string sql = "select " + string.Join(",", typeof(LotsOfNumerics).GetProperties().Select(
x => "cast (1 as " + dbType + ") as [" + x.Name + "]"));
var row = connection.Query<LotsOfNumerics>(sql).Single();
......@@ -3561,6 +3608,64 @@ private void TestBigIntForEverythingWorks<T>(T expected, string dbType)
}
public void SO25069578_DynamicParams_Procs()
{
var parameters = new DynamicParameters();
parameters.Add("foo", "bar");
// parameters = new DynamicParameters(parameters);
try { connection.Execute("drop proc SO25069578"); } catch { }
connection.Execute("create proc SO25069578 @foo nvarchar(max) as select @foo as [X]");
var tran = connection.BeginTransaction(); // gist used transaction; behaves the same either way, though
var row = connection.Query<HazX>("SO25069578", parameters,
commandType: CommandType.StoredProcedure, transaction: tran).Single();
tran.Rollback();
row.X.IsEqualTo("bar");
}
public void Issue149_TypeMismatch_SequentialAccess()
{
string error;
Guid guid = Guid.Parse("cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e");
try
{
var result = connection.Query<Issue149_Person>(@"select @guid as Id", new { guid }).First();
error = null;
} catch(Exception ex)
{
error = ex.Message;
}
error.IsEqualTo("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Object)");
}
public class Issue149_Person { public string Id { get; set; } }
public class HazX
{
public string X { get; set; }
}
public void SO25297173_DynamicIn()
{
var query = @"
declare @table table(value int not null);
insert @table values(1);
insert @table values(2);
insert @table values(3);
insert @table values(4);
insert @table values(5);
insert @table values(6);
insert @table values(7);
SELECT value FROM @table WHERE value IN @myIds";
var queryParams = new Dictionary<string, object> {
{ "myIds", new [] { 5, 6 } }
};
var dynamicParams = new DynamicParameters(queryParams);
List<int> result = connection.Query<int>(query, dynamicParams).ToList();
result.Count.IsEqualTo(2);
result.Contains(5).IsTrue();
result.Contains(6).IsTrue();
}
#if POSTGRESQL
......
......@@ -22,4 +22,12 @@
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.SqlServer.Types" publicKeyToken="89845dcd8080cc91" />
<bindingRedirect oldVersion="10.0.0.0" newVersion="11.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
\ No newline at end of file
......@@ -3,6 +3,7 @@
<package id="EntityFramework" version="6.1.0" targetFramework="net45" />
<package id="FSharp.Core" version="4.0.0" targetFramework="net45" />
<package id="FSPowerPack.Community" version="2.0.0.0" />
<package id="Microsoft.SqlServer.Types" version="11.0.1" targetFramework="net45" />
<package id="Npgsql" version="2.0.11" />
<package id="SqlServerCompact" version="4.0.8482.1" />
</packages>
\ No newline at end of file
......@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata schemaVersion="2">
<id>Dapper</id>
<version>1.28</version>
<version>1.34</version>
<title>Dapper dot net</title>
<authors>Sam Saffron,Marc Gravell</authors>
<owners>Sam Saffron,Marc Gravell</owners>
......@@ -19,6 +19,12 @@
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
</frameworkAssemblies>
<releaseNotes>
* 1.34 - Support for SqlHierarchyId (core)
* 1.33 - Support for SqlGeometry (core) and DbGeometry (EF)
* 1.32 - Support for SqlGeography in core library
* 1.31 - Fix issue with error message when there is a column/type mismatch
* 1.30 - Better async cancellation
* 1.29 - Make underscore name matching optional (opt-in) - this can be a breaking change for some people
* 1.28 - Much better numeric type conversion; fix for large oracle strings; map Foo_Bar to FooBar (etc); ExecuteScalar added; stability fixes
* 1.27 - Fixes for type-handler parse; ensure type-handlers get last dibs on configuring parameters
* 1.26 - New type handler API for extension support
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment