提交 9079b627 编写于 作者: A Adam Schroder

Merge branch 'tbasallo-v5' into v5

......@@ -3,6 +3,8 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NPoco.SqlServer;
......@@ -97,5 +99,49 @@ public override object ProcessDefaultMappings(PocoColumn pocoColumn, object valu
}
return base.ProcessDefaultMappings(pocoColumn, value);
}
public override string FormatCommand(string sql, object[] args)
{
if (sql == null)
return "";
var sb = new StringBuilder();
if (args != null && args.Length > 0)
{
for (int i = 0; i < args.Length; i++)
{
var value = args[i];
var formatted = args[i] as FormattedParameter;
if (formatted != null)
{
value = formatted.Value;
}
var p = new SqlParameter();
ParameterHelper.SetParameterValue(this, p, args[i]);
if (p.Size == 0 || p.SqlDbType == SqlDbType.UniqueIdentifier)
{
if (value == null && (p.SqlDbType == SqlDbType.NVarChar || p.SqlDbType == SqlDbType.VarChar))
{
sb.AppendFormat("DECLARE {0}{1} {2} = null\n", GetParameterPrefix(string.Empty), i, p.SqlDbType);
}
else
{
sb.AppendFormat("DECLARE {0}{1} {2} = '{3}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, value);
}
}
else
{
sb.AppendFormat("DECLARE {0}{1} {2}[{3}] = '{4}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, p.Size, value);
}
}
}
sb.AppendFormat("\n{0}", sql);
return sb.ToString();
}
}
}
\ No newline at end of file
......@@ -2,7 +2,9 @@
using NPoco.SqlServer;
using System;
using System.Threading.Tasks;
using System.Text;
using NPoco.DatabaseTypes;
using System.Data;
namespace NPoco.SqlServer
{
......@@ -39,6 +41,6 @@ protected override async Task<T> ExecutionHookAsync<T>(Func<Task<T>> action)
}
return await base.ExecutionHookAsync(action).ConfigureAwait(false);
}
}
}
}
......@@ -36,7 +36,7 @@ public Database(DbConnection connection)
public Database(DbConnection connection, DatabaseType? dbType)
: this(connection, dbType, null, DefaultEnableAutoSelect)
{ }
public Database(DbConnection connection, DatabaseType? dbType, IsolationLevel? isolationLevel)
: this(connection, dbType, isolationLevel, DefaultEnableAutoSelect)
{ }
......@@ -382,106 +382,17 @@ public virtual void AddParameter(DbCommand cmd, object? value)
var p = cmd.CreateParameter();
p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count);
SetParameterValue(p, value);
cmd.Parameters.Add(p);
}
private void SetParameterValue(DbParameter p, object? value)
{
if (value == null)
{
p.Value = DBNull.Value;
return;
}
// Give the database type first crack at converting to DB required type
value = _dbType.MapParameterValue(value);
var dbtypeSet = false;
var t = value.GetType();
var underlyingT = Nullable.GetUnderlyingType(t);
if (t.GetTypeInfo().IsEnum || (underlyingT != null && underlyingT.GetTypeInfo().IsEnum)) // PostgreSQL .NET driver wont cast enum to int
{
p.Value = (int)value;
}
else if (t == typeof(Guid))
{
p.Value = value;
p.DbType = DbType.Guid;
p.Size = 40;
dbtypeSet = true;
}
else if (t == typeof(string))
{
var strValue = value as string;
if (strValue == null)
{
p.Size = 0;
p.Value = DBNull.Value;
}
else
{
// out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000
if (strValue.Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter")
{
p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null);
}
p.Size = Math.Max(strValue.Length + 1, 4000); // Help query plan caching by using common size
p.Value = value;
}
}
else if (t == typeof(AnsiString))
{
var ansistrValue = value as AnsiString;
if (ansistrValue?.Value == null)
{
p.Size = 0;
p.Value = DBNull.Value;
p.DbType = DbType.AnsiString;
}
else
{
// Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar
p.Size = Math.Max(ansistrValue.Value.Length + 1, 4000);
p.Value = ansistrValue.Value;
p.DbType = DbType.AnsiString;
}
dbtypeSet = true;
}
else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type
{
p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type
p.Value = value;
}
else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type
{
p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type
p.Value = value;
}
else
{
p.Value = value;
}
ParameterHelper.SetParameterValue(_dbType, p, value);
if (!dbtypeSet)
{
var dbType = _dbType.LookupDbType(p.Value.GetTheType(), p.ParameterName);
if (dbType.HasValue)
{
p.DbType = dbType.Value;
}
}
cmd.Parameters.Add(p);
}
// Create a command
private DbCommand CreateCommand(DbConnection connection, string sql, params object[] args)
{
return CreateCommand(connection, CommandType.Text, sql, args);
}
public virtual DbCommand CreateCommand(DbConnection connection, CommandType commandType, string sql, params object[] args)
{
if (commandType == CommandType.StoredProcedure)
......@@ -497,7 +408,7 @@ public virtual DbCommand CreateCommand(DbConnection connection, CommandType comm
// Create the command and add parameters
DbCommand cmd = connection.CreateCommand();
cmd.Connection = connection;
cmd.CommandText = sql;
cmd.CommandText = sql;
cmd.Transaction = _transaction;
foreach (var item in args)
......@@ -621,7 +532,7 @@ private bool OnDeletingInternal(DeleteContext deleteContext)
var result = OnDeleting(deleteContext);
return result && Interceptors.OfType<IDataInterceptor>().All(x => x.OnDeleting(this, deleteContext));
}
public DbCommand CreateStoredProcedureCommand(DbConnection connection, string name, params object[] args)
{
DbCommand cmd = connection.CreateCommand();
......@@ -640,13 +551,13 @@ public DbCommand CreateStoredProcedureCommand(DbConnection connection, string na
else
{
var props = args[0].GetType().GetProperties().Select(x => new { x.Name, Value = x.GetValue(args[0], null) }).ToList();
foreach(var item in props)
foreach (var item in props)
{
DbParameter param = cmd.CreateParameter();
param.ParameterName = item.Name;
SetParameterValue(param, item.Value);
ParameterHelper.SetParameterValue(_dbType, param, item.Value);
cmd.Parameters.Add(param);
}
}
......@@ -700,7 +611,7 @@ public T ExecuteScalar<T>(string sql, params object[] args)
{
return ExecuteScalar<T>(new Sql(sql, args));
}
public T ExecuteScalar<T>(Sql Sql)
{
return ExecuteScalar<T>(Sql.SQL, CommandType.Text, Sql.Arguments);
......@@ -975,8 +886,8 @@ private IEnumerable<T> ReadOneToMany<T>(T instance, DbDataReader r, Expression<F
if (prevPoco != null)
{
if (idFunc != null
&& listFunc != null
if (idFunc != null
&& listFunc != null
&& pocoMember != null
&& idFunc(poco).SequenceEqual(idFunc((T)prevPoco)))
{
......@@ -1054,7 +965,7 @@ internal IEnumerable<T> QueryImp<T>(T instance, Expression<Func<T, IList>>? list
var sql = Sql.SQL;
var args = Sql.Arguments;
if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof (T), sql);
if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof(T), sql);
try
{
......@@ -1176,7 +1087,7 @@ public class DontMap { }
OpenSharedConnectionInternal();
using var cmd = CreateCommand(_sharedConnection, sql, args);
using var r = sync ? ExecuteDataReader(cmd, true).RunSync() : await ExecuteDataReader(cmd, false).ConfigureAwait(false);
var typeIndex = 1;
var list1 = new List<T1>();
var list2 = types.Length > 1 ? new List<T2>() : null;
......@@ -1200,16 +1111,16 @@ public class DontMap { }
switch (typeIndex)
{
case 1:
list1.Add((T1) factory.Map(r, default(T1)));
list1.Add((T1)factory.Map(r, default(T1)));
break;
case 2:
list2!.Add((T2) factory.Map(r, default(T2)));
list2!.Add((T2)factory.Map(r, default(T2)));
break;
case 3:
list3!.Add((T3) factory.Map(r, default(T3)));
list3!.Add((T3)factory.Map(r, default(T3)));
break;
case 4:
list4!.Add((T4) factory.Map(r, default(T4)));
list4!.Add((T4)factory.Map(r, default(T4)));
break;
}
}
......@@ -1226,11 +1137,11 @@ public class DontMap { }
switch (types.Length)
{
case 2:
return ((Func<List<T1>, List<T2>, TRet>) cb)(list1, list2!);
return ((Func<List<T1>, List<T2>, TRet>)cb)(list1, list2!);
case 3:
return ((Func<List<T1>, List<T2>, List<T3>, TRet>) cb)(list1, list2!, list3!);
return ((Func<List<T1>, List<T2>, List<T3>, TRet>)cb)(list1, list2!, list3!);
case 4:
return ((Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet>) cb)(list1, list2!, list3!, list4!);
return ((Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet>)cb)(list1, list2!, list3!, list4!);
}
return default(TRet)!;
......@@ -1261,7 +1172,7 @@ public T SingleOrDefaultById<T>(object primaryKey)
private Sql GenerateSingleByIdSql<T>(object primaryKey)
{
var index = 0;
var pd = PocoDataFactory.ForType(typeof (T));
var pd = PocoDataFactory.ForType(typeof(T));
var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKey, primaryKey is T);
var sql = AutoSelectHelper.AddSelectClause(this, typeof(T), string.Format("WHERE {0}", BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)));
var args = primaryKeyValuePairs.Select(x => x.Value).ToArray();
......@@ -1430,7 +1341,7 @@ private async Task<int> UpdateImpAsync(string tableName, string primaryKeyName,
return result;
}
internal static string BuildPrimaryKeySql(Database database, Dictionary<string, object> primaryKeyValuePair, ref int index)
{
var tempIndex = index;
......@@ -1499,8 +1410,8 @@ public int Update<T>(T poco, Expression<Func<T, object>> fields)
if (poco == null) throw new ArgumentNullException(nameof(poco));
var expression = DatabaseType.ExpressionVisitor<T>(this, PocoDataFactory.ForType(typeof(T)));
expression = expression.Select(fields);
var columnNames = ((ISqlExpression) expression).SelectMembers.Select(x => x.PocoColumn.ColumnName);
var otherNames = ((ISqlExpression) expression).GeneralMembers.Select(x => x.PocoColumn.ColumnName);
var columnNames = ((ISqlExpression)expression).SelectMembers.Select(x => x.PocoColumn.ColumnName);
var otherNames = ((ISqlExpression)expression).GeneralMembers.Select(x => x.PocoColumn.ColumnName);
return Update(poco, columnNames.Union(otherNames));
}
......@@ -1620,7 +1531,7 @@ public bool IsNew<T>(T poco)
return IsNewAsync(poco, true).RunSync();
}
private async Task<bool> IsNewAsync<T>(T poco, bool sync)
private async Task<bool> IsNewAsync<T>(T poco, bool sync)
{
if (poco == null) throw new ArgumentNullException(nameof(poco));
if (poco is System.Dynamic.ExpandoObject || poco is PocoExpando)
......@@ -1638,8 +1549,8 @@ private async Task<bool> IsNewAsync<T>(T poco, bool sync)
}
else if (pd.TableInfo.PrimaryKey.Contains(","))
{
return sync
? !PocoExistsAsync(poco, true).RunSync()
return sync
? !PocoExistsAsync(poco, true).RunSync()
: !await PocoExistsAsync(poco, false).ConfigureAwait(false);
}
else
......@@ -1721,52 +1632,14 @@ void DoPreExecute(DbCommand cmd)
public string LastCommand => FormatCommand(_lastSql, _lastArgs);
private class FormattedParameter
public virtual string FormatCommand(DbCommand cmd)
{
public FormattedParameter(Type type, object value, DbParameter parameter)
{
Type = type;
Value = value;
Parameter = parameter;
}
public Type Type { get; set; }
public object Value { get; set; }
public DbParameter Parameter { get; set; }
}
public string FormatCommand(DbCommand cmd)
{
var parameters = cmd.Parameters.Cast<DbParameter>().Select(parameter =>
{
return new FormattedParameter(parameter.Value.GetTheType(), parameter.Value, parameter);
});
return FormatCommand(cmd.CommandText, parameters.Cast<object>().ToArray());
return _dbType.FormatCommand(cmd);
}
public string FormatCommand(string? sql, object[]? args)
{
var sb = new StringBuilder();
if (sql == null)
return "";
sb.Append(sql);
if (args != null && args.Length > 0)
{
sb.Append("\n");
for (int i = 0; i < args.Length; i++)
{
var type = args[i] != null ? args[i].GetType().Name : string.Empty;
var value = args[i];
if (args[i] is FormattedParameter formatted)
{
type = formatted.Type != null ? formatted.Type.Name : string.Format("{0}, {1}", formatted.Parameter.GetType().Name, formatted.Parameter.DbType);
value = formatted.Value;
}
sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, type, value);
}
sb.Remove(sb.Length - 1, 1);
}
return sb.ToString();
return _dbType.FormatCommand(sql, args);
}
private List<IInterceptor>? _interceptors;
......@@ -1786,7 +1659,7 @@ public IPocoDataFactory PocoDataFactory
set => _pocoDataFactory = value;
}
public string ConnectionString { get { return _connectionString; } }
public string ConnectionString => _connectionString;
// Member variables
private readonly string _connectionString;
......@@ -1860,7 +1733,7 @@ internal static object ProcessMapper(this IDatabase database, PocoColumn pc, obj
var converter = database.Mappers.Find(x => x.GetToDbConverter(pc.ColumnType, pc.MemberInfoData.MemberInfo));
return converter != null ? converter(value) : ProcessDefaultMappings(database, pc, value);
}
internal static object ProcessDefaultMappings(IDatabase database, PocoColumn pocoColumn, object? value)
{
if (pocoColumn.SerializedColumn)
......
......@@ -8,6 +8,7 @@
using NPoco.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using System.Text;
namespace NPoco
{
......@@ -364,5 +365,49 @@ public virtual object ProcessDefaultMappings(PocoColumn pocoColumn, object value
{
return value;
}
internal class FormattedParameter
{
public Type Type { get; set; }
public object Value { get; set; }
public DbParameter Parameter { get; set; }
}
public virtual string FormatCommand(DbCommand cmd)
{
var parameters = cmd.Parameters.Cast<DbParameter>().Select(parameter => new FormattedParameter()
{
Type = parameter.Value.GetTheType(),
Value = parameter.Value,
Parameter = parameter
});
return FormatCommand(cmd.CommandText, parameters.Cast<object>().ToArray());
}
public virtual string FormatCommand(string sql, object[] args)
{
if (sql == null)
return "";
var sb = new StringBuilder();
sb.Append(sql);
if (args != null && args.Length > 0)
{
sb.Append("\n");
for (int i = 0; i < args.Length; i++)
{
var type = args[i] != null ? args[i].GetType().Name : string.Empty;
var value = args[i];
if (args[i] is FormattedParameter formatted)
{
type = formatted.Type != null ? formatted.Type.Name : string.Format("{0}, {1}", formatted.Parameter.GetType().Name, formatted.Parameter.DbType);
value = formatted.Value;
}
sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", GetParameterPrefix(string.Empty), i, type, value);
}
sb.Remove(sb.Length - 1, 1);
}
return sb.ToString();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
......@@ -133,5 +135,94 @@ private static string ProcessParam(ref string sql, string rawParam, object[] arg
return "@" + (args_dest.Count - 1).ToString();
}
}
public static void SetParameterValue(DatabaseType dbType, DbParameter p, object value)
{
if (value == null)
{
p.Value = DBNull.Value;
return;
}
// Give the database type first crack at converting to DB required type
value = dbType.MapParameterValue(value);
var dbtypeSet = false;
var t = value.GetType();
var underlyingT = Nullable.GetUnderlyingType(t);
if (t.GetTypeInfo().IsEnum || (underlyingT != null && underlyingT.GetTypeInfo().IsEnum)) // PostgreSQL .NET driver wont cast enum to int
{
p.Value = (int)value;
}
else if (t == typeof(Guid))
{
p.Value = value;
p.DbType = DbType.Guid;
p.Size = 40;
dbtypeSet = true;
}
else if (t == typeof(string))
{
var strValue = value as string;
if (strValue == null)
{
p.Size = 0;
p.Value = DBNull.Value;
}
else
{
// out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000
if (strValue.Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter")
{
p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null);
}
p.Size = Math.Max(strValue.Length + 1, 4000); // Help query plan caching by using common size
p.Value = value;
}
}
else if (t == typeof(AnsiString))
{
var ansistrValue = value as AnsiString;
if (ansistrValue?.Value == null)
{
p.Size = 0;
p.Value = DBNull.Value;
p.DbType = DbType.AnsiString;
}
else
{
// Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar
p.Size = Math.Max(ansistrValue.Value.Length + 1, 4000);
p.Value = ansistrValue.Value;
p.DbType = DbType.AnsiString;
}
dbtypeSet = true;
}
else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type
{
p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type
p.Value = value;
}
else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type
{
p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type
p.Value = value;
}
else
{
p.Value = value;
}
if (!dbtypeSet)
{
var dbTypeLookup = dbType.LookupDbType(p.Value.GetTheType(), p.ParameterName);
if (dbTypeLookup.HasValue)
{
p.DbType = dbTypeLookup.Value;
}
}
}
}
}
using System.Data;
using Microsoft.Data.SqlClient;
using NPoco.DatabaseTypes;
using NUnit.Framework;
namespace NPoco.Tests
......@@ -33,7 +32,7 @@ public void FormattingWithStringValue()
public class MyDb : Database
{
public MyDb()
: base("test", new SqlServerDatabaseType(), SqlClientFactory.Instance)
: base("test", DatabaseType.MySQL, SqlClientFactory.Instance)
{
}
}
......
using System;
using System.Data;
using Microsoft.Data.SqlClient;
using NPoco.DatabaseTypes;
using NUnit.Framework;
namespace NPoco.Tests
{
public class FormatSqlServerCommandTest
{
[Test]
public void FormattingWithSqlParameterThatIsNotNull()
{
var cmd = new SqlCommand();
cmd.Parameters.Add(new SqlParameter("Test", SqlDbType.NVarChar));
var db = new MyDb();
Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" }));
Assert.AreEqual("DECLARE @0 Int = '32'\n\nsql @0", db.FormatCommand("sql @0", new object[] { 32 }));
Assert.AreEqual("DECLARE @0 DateTime = '" + DateTime.Today + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { DateTime.Today }));
var guid = Guid.NewGuid();
Assert.AreEqual("DECLARE @0 UniqueIdentifier = '" + guid + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { guid }));
}
[Test]
public void FormattingWithSqlParameterThatIsNotNullSqlServerDatabase()
{
var cmd = new SqlCommand();
cmd.Parameters.Add(new SqlParameter("Test", SqlDbType.NVarChar));
var db = new MyDb2();
Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" }));
Assert.AreEqual("DECLARE @0 Int = '32'\n\nsql @0", db.FormatCommand("sql @0", new object[] { 32 }));
Assert.AreEqual("DECLARE @0 DateTime = '" + DateTime.Today + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { DateTime.Today }));
var guid = Guid.NewGuid();
Assert.AreEqual("DECLARE @0 UniqueIdentifier = '" + guid + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { guid }));
}
[Test]
public void FormattingWithNullValue()
{
var db = new MyDb();
Assert.AreEqual("DECLARE @0 NVarChar = null\n\nsql @0", db.FormatCommand("sql @0", new object[] { null }));
}
[Test]
public void FormattingWithNullValueSqlServerDatabase()
{
var db = new MyDb2();
Assert.AreEqual("DECLARE @0 NVarChar = null\n\nsql @0", db.FormatCommand("sql @0", new object[] { null }));
}
[Test]
public void FormattingWithStringValue()
{
var db = new MyDb();
Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" }));
}
[Test]
public void FormattingWithStringValueSqlServerDatabase()
{
var db = new MyDb2();
Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" }));
}
public class MyDb2 : NPoco.SqlServer.SqlServerDatabase
{
public MyDb2()
: base("test")
{
}
}
public class MyDb : Database
{
public MyDb()
: base("test", new SqlServerDatabaseType(), SqlClientFactory.Instance)
{
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册