diff --git a/.gitignore b/.gitignore index ef6059592f97446c52465ede307c7e4c884379df..5467ebe9e2a16ce8c8df67afff0a91f8bd220738 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ obj/ .vs/WLib/v15/.suo .vs/WLib/v15/Server/sqlite3/storage.ide-shm .vs/WLib/v15/Server/sqlite3/storage.ide-wal +licenses.licx +Apps diff --git a/App/Config/Params/config.db b/App/Config/Params/config.db new file mode 100644 index 0000000000000000000000000000000000000000..f502bd4729059d699294348698a4d02b1ba9d501 Binary files /dev/null and b/App/Config/Params/config.db differ diff --git "a/App/Config/Params/\344\270\211\350\260\203\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\277\350\245\277\344\270\211\350\260\203.db" "b/App/Config/Params/\344\270\211\350\260\203\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\277\350\245\277\344\270\211\350\260\203.db" new file mode 100644 index 0000000000000000000000000000000000000000..f502bd4729059d699294348698a4d02b1ba9d501 Binary files /dev/null and "b/App/Config/Params/\344\270\211\350\260\203\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\277\350\245\277\344\270\211\350\260\203.db" differ diff --git "a/App/Config/Params/\345\271\264\345\272\246\345\217\230\346\233\264\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\264\345\272\246\345\217\230\346\233\264.db" "b/App/Config/Params/\345\271\264\345\272\246\345\217\230\346\233\264\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\264\345\272\246\345\217\230\346\233\264.db" new file mode 100644 index 0000000000000000000000000000000000000000..f502bd4729059d699294348698a4d02b1ba9d501 Binary files /dev/null and "b/App/Config/Params/\345\271\264\345\272\246\345\217\230\346\233\264\345\217\202\346\225\260\346\226\271\346\241\210/\345\271\264\345\272\246\345\217\230\346\233\264.db" differ diff --git a/App/Config/config.db b/App/Config/config.db new file mode 100644 index 0000000000000000000000000000000000000000..f502bd4729059d699294348698a4d02b1ba9d501 Binary files /dev/null and b/App/Config/config.db differ diff --git a/DLL/System.ValueTuple.dll b/DLL/System.ValueTuple.dll new file mode 100644 index 0000000000000000000000000000000000000000..65fa9eeead65ebcc4063900e21795b6fff6ed687 Binary files /dev/null and b/DLL/System.ValueTuple.dll differ diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs new file mode 100644 index 0000000000000000000000000000000000000000..b891f93fb351e23f9bb8d4680462b44cc47232d2 --- /dev/null +++ b/Dapper/CommandDefinition.cs @@ -0,0 +1,185 @@ +using System; +using System.Data; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace Dapper +{ + /// + /// Represents the key aspects of a sql operation + /// + public struct CommandDefinition + { + internal static CommandDefinition ForCallback(object parameters) + { + if (parameters is DynamicParameters) + { + return new CommandDefinition(parameters); + } + else + { + return default(CommandDefinition); + } + } + + internal void OnCompleted() + { + (Parameters as SqlMapper.IParameterCallbacks)?.OnCompleted(); + } + + /// + /// The command (sql or a stored-procedure name) to execute + /// + public string CommandText { get; } + + /// + /// The parameters associated with the command + /// + public object Parameters { get; } + + /// + /// The active transaction for the command + /// + public IDbTransaction Transaction { get; } + + /// + /// The effective timeout for the command + /// + public int? CommandTimeout { get; } + + /// + /// The type of command that the command-text represents + /// + public CommandType? CommandType { get; } + + /// + /// Should data be buffered before returning? + /// + public bool Buffered => (Flags & CommandFlags.Buffered) != 0; + + /// + /// Should the plan for this query be cached? + /// + internal bool AddToCache => (Flags & CommandFlags.NoCache) == 0; + + /// + /// Additional state flags against this command + /// + public CommandFlags Flags { get; } + + /// + /// Can async queries be pipelined? + /// + public bool Pipelined => (Flags & CommandFlags.Pipelined) != 0; + + /// + /// Initialize the command definition + /// + /// The text for this command. + /// The parameters for this command. + /// The transaction for this command to participate in. + /// The timeout (in seconds) for this command. + /// The for this command. + /// The behavior flags for this command. + /// The cancellation token for this command. + public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, + CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered + , CancellationToken cancellationToken = default(CancellationToken) + ) + { + CommandText = commandText; + Parameters = parameters; + Transaction = transaction; + CommandTimeout = commandTimeout; + CommandType = commandType; + Flags = flags; + CancellationToken = cancellationToken; + } + + private CommandDefinition(object parameters) : this() + { + Parameters = parameters; + } + + /// + /// For asynchronous operations, the cancellation-token + /// + public CancellationToken CancellationToken { get; } + + internal IDbCommand SetupCommand(IDbConnection cnn, Action paramReader) + { + var cmd = cnn.CreateCommand(); + var init = GetInit(cmd.GetType()); + init?.Invoke(cmd); + if (Transaction != null) + cmd.Transaction = Transaction; + cmd.CommandText = CommandText; + if (CommandTimeout.HasValue) + { + cmd.CommandTimeout = CommandTimeout.Value; + } + else if (SqlMapper.Settings.CommandTimeout.HasValue) + { + cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value; + } + if (CommandType.HasValue) + cmd.CommandType = CommandType.Value; + paramReader?.Invoke(cmd, Parameters); + return cmd; + } + + private static SqlMapper.Link> commandInitCache; + + private static Action GetInit(Type commandType) + { + if (commandType == null) + return null; // GIGO + if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out Action action)) + { + return action; + } + var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool)); + var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int)); + + action = null; + if (bindByName != null || initialLongFetchSize != null) + { + var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) }); + var il = method.GetILGenerator(); + + if (bindByName != null) + { + // .BindByName = true + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, commandType); + il.Emit(OpCodes.Ldc_I4_1); + il.EmitCall(OpCodes.Callvirt, bindByName, null); + } + if (initialLongFetchSize != null) + { + // .InitialLONGFetchSize = -1 + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, commandType); + il.Emit(OpCodes.Ldc_I4_M1); + il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null); + } + il.Emit(OpCodes.Ret); + action = (Action)method.CreateDelegate(typeof(Action)); + } + // cache it + SqlMapper.Link>.TryAdd(ref commandInitCache, commandType, ref action); + return action; + } + + private static MethodInfo GetBasicPropertySetter(Type declaringType, string name, Type expectedType) + { + var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); + if (prop?.CanWrite == true && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0) + { + return prop.GetSetMethod(); + } + return null; + } + } +} diff --git a/Dapper/CommandFlags.cs b/Dapper/CommandFlags.cs new file mode 100644 index 0000000000000000000000000000000000000000..d85a384f7295e2bb96b0fc3c5dd420b2db17fb8c --- /dev/null +++ b/Dapper/CommandFlags.cs @@ -0,0 +1,28 @@ +using System; + +namespace Dapper +{ + /// + /// Additional state flags that control command behaviour + /// + [Flags] + public enum CommandFlags + { + /// + /// No additional flags + /// + None = 0, + /// + /// Should data be buffered before returning? + /// + Buffered = 1, + /// + /// Can async queries be pipelined? + /// + Pipelined = 2, + /// + /// Should the plan cache be bypassed? + /// + NoCache = 4, + } +} diff --git a/Dapper/Custom/ForeignKeyAttribute.cs b/Dapper/Custom/ForeignKeyAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..20fd5a8d977b0a356feee8727a7124228d1f8a57 --- /dev/null +++ b/Dapper/Custom/ForeignKeyAttribute.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dapper +{ + /// + /// 表示外键 + /// + [AttributeUsage(AttributeTargets.Property)] + public class ForeignKeyAttribute : Attribute + { + /// + /// 外键所关联的表 + /// + public string ForeignTable { get; set; } + /// + /// 外键所关联的表的字段 + /// + public string ForeignField { get; set; } + /// + /// 表示外键 + /// + /// 外键信息,格式为“ForeignTable.ForeginColumn” + public ForeignKeyAttribute(string foreignTable) + { + ForeignTable = foreignTable; + } + /// + /// 表示外键 + /// + /// 外键所关联的表 + /// 外键所关联的表的字段 + public ForeignKeyAttribute(string foreignTable, string foreignField) + { + ForeignTable = foreignTable; + ForeignField = foreignField; + } + } +} diff --git a/Dapper/Custom/KeyAttribute.cs b/Dapper/Custom/KeyAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..14f91b652827bb95596fa7b4d043593f55e4d447 --- /dev/null +++ b/Dapper/Custom/KeyAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dapper +{ + /// + /// 表示主键 + /// + [AttributeUsage(AttributeTargets.Property)] + public class KeyAttribute : Attribute + { + } +} diff --git a/Dapper/Custom/SqlMapper.Extension.cs b/Dapper/Custom/SqlMapper.Extension.cs new file mode 100644 index 0000000000000000000000000000000000000000..2840d64575fbddca57602502f224bdbfd37d2e8d --- /dev/null +++ b/Dapper/Custom/SqlMapper.Extension.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Dapper +{ + /* + * 自定义对Dapper组件的扩展,添加减少SQL语句对数据库进行最基本的增删改查的方法、where条件语句构造方法 + */ + + public static partial class SqlMapper + { + #region 基本ORM增删改查 + /// + /// 根据类名查询同名的表,转成指定类型数据 + /// + /// + /// + /// + /// + public static IEnumerable SimpleQuery(this IDbConnection dbConnection, string whereClause = null) where T : class + { + whereClause = string.IsNullOrWhiteSpace(whereClause) ? string.Empty : "where " + whereClause; + return dbConnection.Query($"select * from {typeof(T).Name} {whereClause}"); + } + /// + /// 根据类名向同名的表插入数据 + /// + /// + /// + /// 要插入的对象或对象数组(IEnumerable) + /// + public static int SimpleInsert(this IDbConnection dbConnection, T value) + { + var t = typeof(T); + var @params = t.GetProperties().Select(v => "@" + v.Name).Aggregate((a, b) => a + "," + b); + return dbConnection.Execute($"Insert into {t.Name} values ({@params})", value); + } + /// + /// 根据类名更新同名的表的指定数据 + /// + /// + /// + /// 要更新的对象或对象数组(IEnumerable) + /// + public static int SimpleUpdate(this IDbConnection dbConnection, T value) + { + var t = typeof(T); + var properties = t.GetProperties(); + var @params = properties.Select(v => $"{v.Name} = @{v.Name}").Aggregate((a, b) => a + "," + b); + var keyProperty = properties.FirstOrDefault(v => v.GetCustomAttributes(typeof(KeyAttribute), false) != null); + if (keyProperty == null) + throw new System.Exception($"类型{t.Name}未定义主键(Key),无法执行{nameof(SimpleUpdate)}操作"); + + var whereClause = $"{keyProperty.Name} = @{keyProperty.Name}"; + return dbConnection.Execute($"update {t.Name} set {@params} where {whereClause}", value); + } + /// + /// 根据类名删除同名的表的指定数据 + /// + /// + /// + /// 要删除的对象或对象数组(IEnumerable) + /// + public static int SimpleDelete(this IDbConnection dbConnection, T value) + { + var t = typeof(T); + var properties = t.GetProperties(); + var keyProperty = properties.FirstOrDefault(v => v.GetCustomAttributes(typeof(KeyAttribute), false) != null); + if (keyProperty == null) + throw new System.Exception($"类型{t.Name}未定义主键(Key),无法执行{nameof(SimpleUpdate)}操作"); + + var whereClause = $"{keyProperty.Name} = @{keyProperty.Name}"; + return dbConnection.Execute($"delete from {t.Name} where {whereClause}", value); + } + #endregion + + + #region 条件查询语句构建 + public static string And(this string str, string fieldName) => $"{str} and {fieldName}"; + public static string Or(this string str, string fieldName) => $"{str} or {fieldName}"; + public static string Like(this string str, string value) => $"{str} like '{value}'"; + public static string In(this string str, params object[] values) + { + if (values == null || values.Length == 0) + throw new System.ArgumentException($"参数{values}不能为null或数组个数为0!"); + + bool isStr = values[0] is string; + var inValues = string.Empty; + if (isStr) + inValues = values.Select(v => $"'{v}'").Aggregate((a, b) => a + "," + b); + else + inValues = values.Select(v => v.ToString()).Aggregate((a, b) => a + "," + b); + return $"{str} In ({inValues})"; + } + public static string Equal(this string str, object value) => value is string ? $"{str} = '{value}'" : $"{str} = {value}"; + public static string NotEqual(this string str, object value) => value is string ? $"{str} <> '{value}'" : $"{str} <> {value}"; + public static string GreaterThan(this string str, object value) => value is string ? $"{str} > '{value}'" : $"{str} > {value}"; + public static string GreaterThanEqual(this string str, object value) => value is string ? $"{str} >= '{value}'" : $"{str} >= {value}"; + public static string LessThan(this string str, object value) => value is string ? $"{str} < '{value}'" : $"{str} < {value}"; + public static string LessThanEqual(this string str, object value) => value is string ? $"{str} <= '{value}'" : $"{str} <= {value}"; + #endregion + } +} diff --git a/Dapper/CustomPropertyTypeMap.cs b/Dapper/CustomPropertyTypeMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..629cf14dcc2c9345a089e9ad0cd72fe65211f829 --- /dev/null +++ b/Dapper/CustomPropertyTypeMap.cs @@ -0,0 +1,62 @@ +using System; +using System.Reflection; + +namespace Dapper +{ + /// + /// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping) + /// + public sealed class CustomPropertyTypeMap : SqlMapper.ITypeMap + { + private readonly Type _type; + private readonly Func _propertySelector; + + /// + /// Creates custom property mapping + /// + /// Target entity type + /// Property selector based on target type and DataReader column name + public CustomPropertyTypeMap(Type type, Func propertySelector) + { + _type = type ?? throw new ArgumentNullException(nameof(type)); + _propertySelector = propertySelector ?? throw new ArgumentNullException(nameof(propertySelector)); + } + + /// + /// Always returns default constructor + /// + /// DataReader column names + /// DataReader column types + /// Default constructor + public ConstructorInfo FindConstructor(string[] names, Type[] types) => + _type.GetConstructor(new Type[0]); + + /// + /// Always returns null + /// + /// + public ConstructorInfo FindExplicitConstructor() => null; + + /// + /// Not implemented as far as default constructor used for all cases + /// + /// + /// + /// + public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) + { + throw new NotSupportedException(); + } + + /// + /// Returns property based on selector strategy + /// + /// DataReader column name + /// Poperty member map + public SqlMapper.IMemberMap GetMember(string columnName) + { + var prop = _propertySelector(_type, columnName); + return prop != null ? new SimpleMemberMap(columnName, prop) : null; + } + } +} diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj new file mode 100644 index 0000000000000000000000000000000000000000..ad8c8c9e4e132ea447703e272db8f07538a899a8 --- /dev/null +++ b/Dapper/Dapper.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928} + Library + Properties + Dapper + Dapper + v4.5 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dapper/DataTableHandler.cs b/Dapper/DataTableHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..817961500705c8f83882ab23b718246d606929c2 --- /dev/null +++ b/Dapper/DataTableHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Data; +#if !NETSTANDARD1_3 +namespace Dapper +{ + internal sealed class DataTableHandler : SqlMapper.ITypeHandler + { + public object Parse(Type destinationType, object value) + { + throw new NotImplementedException(); + } + + public void SetValue(IDbDataParameter parameter, object value) + { + TableValuedParameter.Set(parameter, value as DataTable, null); + } + } +} +#endif \ No newline at end of file diff --git a/Dapper/DbString.cs b/Dapper/DbString.cs new file mode 100644 index 0000000000000000000000000000000000000000..cec7291962ec3c5cfc36c9bf4445909149e54ce1 --- /dev/null +++ b/Dapper/DbString.cs @@ -0,0 +1,87 @@ +using System; +using System.Data; + +namespace Dapper +{ + /// + /// 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 + /// + public sealed class DbString : SqlMapper.ICustomQueryParameter + { + /// + /// Default value for IsAnsi. + /// + public static bool IsAnsiDefault { get; set; } + + /// + /// A value to set the default value of strings + /// going through Dapper. Default is 4000, any value larger than this + /// field will not have the default value applied. + /// + public const int DefaultLength = 4000; + + /// + /// Create a new DbString + /// + public DbString() + { + Length = -1; + IsAnsi = IsAnsiDefault; + } + /// + /// Ansi vs Unicode + /// + public bool IsAnsi { get; set; } + /// + /// Fixed length + /// + public bool IsFixedLength { get; set; } + /// + /// Length of the string -1 for max + /// + public int Length { get; set; } + /// + /// The value of the string + /// + public string Value { get; set; } + /// + /// Add the parameter to the command... internal use only + /// + /// + /// + public void AddParameter(IDbCommand command, string name) + { + if (IsFixedLength && Length == -1) + { + throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); + } + bool add = !command.Parameters.Contains(name); + IDbDataParameter param; + if (add) + { + param = command.CreateParameter(); + param.ParameterName = name; + } + else + { + param = (IDbDataParameter)command.Parameters[name]; + } +#pragma warning disable 0618 + param.Value = SqlMapper.SanitizeParameterValue(Value); +#pragma warning restore 0618 + if (Length == -1 && Value != null && Value.Length <= DefaultLength) + { + param.Size = DefaultLength; + } + else + { + param.Size = Length; + } + param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); + if (add) + { + command.Parameters.Add(param); + } + } + } +} diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..277c678847e9ebb5ba09d65e35aca3b6deb40bdb --- /dev/null +++ b/Dapper/DefaultTypeMap.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Dapper +{ + /// + /// Represents default type mapping strategy used by Dapper + /// + public sealed class DefaultTypeMap : SqlMapper.ITypeMap + { + private readonly List _fields; + private readonly Type _type; + + /// + /// Creates default type map + /// + /// Entity type + public DefaultTypeMap(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + _fields = GetSettableFields(type); + Properties = GetSettableProps(type); + _type = type; + } +#if NETSTANDARD1_3 + private static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y) + { + if (ReferenceEquals(x, y)) return true; + if (x == null || y == null) return false; + if (x.Length != y.Length) return false; + for (int i = 0; i < x.Length; i++) + if (x[i].ParameterType != y[i].ParameterType) return false; + return true; + } +#endif + internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type) + { + if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true); +#if NETSTANDARD1_3 + return propertyInfo.DeclaringType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Single(x => x.Name == propertyInfo.Name + && x.PropertyType == propertyInfo.PropertyType + && IsParameterMatch(x.GetIndexParameters(), propertyInfo.GetIndexParameters()) + ).GetSetMethod(true); +#else + return propertyInfo.DeclaringType.GetProperty( + propertyInfo.Name, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + Type.DefaultBinder, + propertyInfo.PropertyType, + propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), + null).GetSetMethod(true); +#endif + } + + internal static List GetSettableProps(Type t) + { + return t + .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(p => GetPropertySetter(p, t) != null) + .ToList(); + } + + internal static List GetSettableFields(Type t) + { + return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); + } + + /// + /// Finds best constructor + /// + /// DataReader column names + /// DataReader column types + /// Matching constructor or default one + 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] && !SqlMapper.HasTypeHandler(unboxedType)) + && !(unboxedType.IsEnum() && Enum.GetUnderlyingType(unboxedType) == types[i]) + && !(unboxedType == typeof(char) && types[i] == typeof(string)) + && !(unboxedType.IsEnum() && types[i] == typeof(string))) + { + break; + } + } + + if (i == ctorParameters.Length) + return ctor; + } + + return null; + } + + /// + /// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it. + /// + public ConstructorInfo FindExplicitConstructor() + { + var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); +#if NETSTANDARD1_3 + var withAttr = constructors.Where(c => c.CustomAttributes.Any(x => x.AttributeType == typeof(ExplicitConstructorAttribute))).ToList(); +#else + var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); +#endif + + if (withAttr.Count == 1) + { + return withAttr[0]; + } + + return null; + } + + /// + /// Gets mapping for constructor parameter + /// + /// Constructor to resolve + /// DataReader column name + /// Mapping implementation + 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))); + } + + /// + /// Gets member mapping for column + /// + /// DataReader column name + /// Mapping implementation + public SqlMapper.IMemberMap GetMember(string columnName) + { + var property = Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); + + if (property == null && MatchNamesWithUnderscores) + { + property = Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal)) + ?? Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase)); + } + + if (property != null) + return new SimpleMemberMap(columnName, property); + + // roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField; + var backingFieldName = "<" + columnName + ">k__BackingField"; + + // preference order is: + // exact match over underscre match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores + var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + + if (field == null && MatchNamesWithUnderscores) + { + var effectiveColumnName = columnName.Replace("_", ""); + backingFieldName = "<" + effectiveColumnName + ">k__BackingField"; + + field = _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + } + + if (field != null) + return new SimpleMemberMap(columnName, field); + + return null; + } + /// + /// Should column names like User_Id be allowed to match properties/fields like UserId ? + /// + public static bool MatchNamesWithUnderscores { get; set; } + + /// + /// The settable properties for this typemap + /// + public List Properties { get; } + } +} diff --git a/Dapper/DynamicParameters.CachedOutputSetters.cs b/Dapper/DynamicParameters.CachedOutputSetters.cs new file mode 100644 index 0000000000000000000000000000000000000000..aff2a4cfcace9d34b4d979c2a027ee5909e69c21 --- /dev/null +++ b/Dapper/DynamicParameters.CachedOutputSetters.cs @@ -0,0 +1,16 @@ +using System.Collections; + +namespace Dapper +{ + public partial class DynamicParameters + { + // The type here is used to differentiate the cache by type via generics + // ReSharper disable once UnusedTypeParameter + internal static class CachedOutputSetters + { + // Intentional, abusing generics to get our cache splits + // ReSharper disable once StaticMemberInGenericType + public static readonly Hashtable Cache = new Hashtable(); + } + } +} diff --git a/Dapper/DynamicParameters.ParamInfo.cs b/Dapper/DynamicParameters.ParamInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..32fa1ab30f62a5da7895c31e7036cc338935aea7 --- /dev/null +++ b/Dapper/DynamicParameters.ParamInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Data; + +namespace Dapper +{ + public partial class DynamicParameters + { + private sealed 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; } + internal Action OutputCallback { get; set; } + internal object OutputTarget { get; set; } + internal bool CameFromTemplate { get; set; } + + public byte? Precision { get; set; } + public byte? Scale { get; set; } + } + } +} diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..2b8908bbc80e4e48922aa8503012061de64b4824 --- /dev/null +++ b/Dapper/DynamicParameters.cs @@ -0,0 +1,504 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; + +#if NETSTANDARD1_3 +using ApplicationException = System.InvalidOperationException; +#endif + +namespace Dapper +{ + /// + /// A bag of parameters that can be passed to the Dapper Query and Execute methods + /// + public partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks + { + internal const DbType EnumerableMultiParameter = (DbType)(-1); + private static readonly Dictionary> paramReaderCache = new Dictionary>(); + private readonly Dictionary parameters = new Dictionary(); + private List templates; + + object SqlMapper.IParameterLookup.this[string name] => + parameters.TryGetValue(name, out ParamInfo param) ? param.Value : null; + + /// + /// construct a dynamic parameter bag + /// + public DynamicParameters() + { + RemoveUnused = true; + } + + /// + /// construct a dynamic parameter bag + /// + /// can be an anonymous type or a DynamicParameters bag + public DynamicParameters(object template) + { + RemoveUnused = true; + AddDynamicParams(template); + } + + /// + /// 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 + /// + /// + public void AddDynamicParams(object param) + { + var obj = param; + if (obj != null) + { + var subDynamic = obj as DynamicParameters; + if (subDynamic == null) + { + var dictionary = obj as IEnumerable>; + if (dictionary == null) + { + templates = templates ?? new List(); + templates.Add(obj); + } + else + { + foreach (var kvp in dictionary) + { + Add(kvp.Key, kvp.Value, null, null, null); + } + } + } + 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(); + foreach (var t in subDynamic.templates) + { + templates.Add(t); + } + } + } + } + } + + /// + /// Add a parameter to this dynamic parameter list. + /// + /// The name of the parameter. + /// The value of the parameter. + /// The type of the parameter. + /// The in or out direction of the parameter. + /// The size of the parameter. + public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size) + { + parameters[Clean(name)] = new ParamInfo + { + Name = name, + Value = value, + ParameterDirection = direction ?? ParameterDirection.Input, + DbType = dbType, + Size = size + }; + } + + /// + /// Add a parameter to this dynamic parameter list. + /// + /// The name of the parameter. + /// The value of the parameter. + /// The type of the parameter. + /// The in or out direction of the parameter. + /// The size of the parameter. + /// The precision of the parameter. + /// The scale of the parameter. + public void Add(string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) + { + parameters[Clean(name)] = new ParamInfo + { + Name = name, + Value = value, + ParameterDirection = direction ?? ParameterDirection.Input, + DbType = dbType, + Size = size, + Precision = precision, + Scale = scale + }; + } + + private 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); + } + + /// + /// If true, the command-text is inspected and only values that are clearly used are included on the connection + /// + public bool RemoveUnused { get; set; } + + /// + /// Add all the parameters needed to the command just before it executes + /// + /// The raw command prior to execution + /// Information about the query + 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 appender; + + lock (paramReaderCache) + { + if (!paramReaderCache.TryGetValue(newIdent, out appender)) + { + appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals); + paramReaderCache[newIdent] = appender; + } + } + + appender(command, template); + } + + // The parameters were added to the command, but not the + // DynamicParameters until now. + foreach (IDbDataParameter param in command.Parameters) + { + // If someone makes a DynamicParameters with a template, + // then explicitly adds a parameter of a matching name, + // it will already exist in 'parameters'. + if (!parameters.ContainsKey(param.ParameterName)) + { + parameters.Add(param.ParameterName, new ParamInfo + { + AttachedParam = param, + CameFromTemplate = true, + DbType = param.DbType, + Name = param.ParameterName, + ParameterDirection = param.Direction, + Size = param.Size, + Value = param.Value + }); + } + } + + // Now that the parameters are added to the command, let's place our output callbacks + var tmp = outputCallbacks; + if (tmp != null) + { + foreach (var generator in tmp) + { + generator(); + } + } + } + + foreach (var param in parameters.Values) + { + if (param.CameFromTemplate) continue; + + var dbType = param.DbType; + var val = param.Value; + string name = Clean(param.Name); + var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; + + SqlMapper.ITypeHandler handler = null; + if (dbType == null && val != null && !isCustomQueryParameter) + { +#pragma warning disable 618 + dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler); +#pragma warning disable 618 + } + if (isCustomQueryParameter) + { + ((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name); + } + else if (dbType == EnumerableMultiParameter) + { +#pragma warning disable 612, 618 + SqlMapper.PackListParameters(command, name, val); +#pragma warning restore 612, 618 + } + else + { + bool add = !command.Parameters.Contains(name); + IDbDataParameter p; + if (add) + { + p = command.CreateParameter(); + p.ParameterName = name; + } + else + { + p = (IDbDataParameter)command.Parameters[name]; + } + + p.Direction = param.ParameterDirection; + if (handler == null) + { +#pragma warning disable 0618 + p.Value = SqlMapper.SanitizeParameterValue(val); +#pragma warning restore 0618 + if (dbType != null && p.DbType != dbType) + { + p.DbType = dbType.Value; + } + var s = val as string; + if (s?.Length <= DbString.DefaultLength) + { + p.Size = DbString.DefaultLength; + } + if (param.Size != null) p.Size = param.Size.Value; + if (param.Precision != null) p.Precision = param.Precision.Value; + if (param.Scale != null) p.Scale = param.Scale.Value; + } + else + { + if (dbType != null) p.DbType = dbType.Value; + if (param.Size != null) p.Size = param.Size.Value; + if (param.Precision != null) p.Precision = param.Precision.Value; + if (param.Scale != null) p.Scale = param.Scale.Value; + handler.SetValue(p, val ?? DBNull.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); + } + + /// + /// All the names of the param in the bag, use Get to yank them out + /// + public IEnumerable ParameterNames => parameters.Select(p => p.Key); + + /// + /// Get the value of a parameter + /// + /// + /// + /// The value, note DBNull.Value is not returned, instead the value is returned as null + public T Get(string name) + { + var paramInfo = parameters[Clean(name)]; + var attachedParam = paramInfo.AttachedParam; + object val = attachedParam == null ? paramInfo.Value : attachedParam.Value; + if (val == DBNull.Value) + { + if (default(T) != null) + { + throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); + } + return default(T); + } + return (T)val; + } + + /// + /// Allows you to automatically populate a target property/field from output parameters. It actually + /// creates an InputOutput parameter, so you can still pass data in. + /// + /// + /// The object whose property/field you wish to populate. + /// A MemberExpression targeting a property/field of the target (or descendant thereof.) + /// + /// The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings. + /// The DynamicParameters instance + public DynamicParameters Output(T target, Expression> expression, DbType? dbType = null, int? size = null) + { + var failMessage = "Expression must be a property/field chain off of a(n) {0} instance"; + failMessage = string.Format(failMessage, typeof(T).Name); + Action @throw = () => throw new InvalidOperationException(failMessage); + + // Is it even a MemberExpression? + var lastMemberAccess = expression.Body as MemberExpression; + + if (lastMemberAccess == null + || (!(lastMemberAccess.Member is PropertyInfo) + && !(lastMemberAccess.Member is FieldInfo))) + { + if (expression.Body.NodeType == ExpressionType.Convert + && expression.Body.Type == typeof(object) + && ((UnaryExpression)expression.Body).Operand is MemberExpression) + { + // It's got to be unboxed + lastMemberAccess = (MemberExpression)((UnaryExpression)expression.Body).Operand; + } + else + { + @throw(); + } + } + + // Does the chain consist of MemberExpressions leading to a ParameterExpression of type T? + MemberExpression diving = lastMemberAccess; + // Retain a list of member names and the member expressions so we can rebuild the chain. + List names = new List(); + List chain = new List(); + + do + { + // Insert the names in the right order so expression + // "Post.Author.Name" becomes parameter "PostAuthorName" + names.Insert(0, diving?.Member.Name); + chain.Insert(0, diving); + + var constant = diving?.Expression as ParameterExpression; + diving = diving?.Expression as MemberExpression; + + if (constant != null && constant.Type == typeof(T)) + { + break; + } + else if (diving == null + || (!(diving.Member is PropertyInfo) + && !(diving.Member is FieldInfo))) + { + @throw(); + } + } + while (diving != null); + + var dynamicParamName = string.Concat(names.ToArray()); + + // Before we get all emitty... + var lookup = string.Join("|", names.ToArray()); + + var cache = CachedOutputSetters.Cache; + var setter = (Action)cache[lookup]; + if (setter != null) goto MAKECALLBACK; + + // Come on let's build a method, let's build it, let's build it now! + var dm = new DynamicMethod("ExpressionParam" + Guid.NewGuid().ToString(), null, new[] { typeof(object), GetType() }, true); + var il = dm.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // [object] + il.Emit(OpCodes.Castclass, typeof(T)); // [T] + + // Count - 1 to skip the last member access + var i = 0; + for (; i < (chain.Count - 1); i++) + { + var member = chain[0].Member; + + if (member is PropertyInfo) + { + var get = ((PropertyInfo)member).GetGetMethod(true); + il.Emit(OpCodes.Callvirt, get); // [Member{i}] + } + else // Else it must be a field! + { + il.Emit(OpCodes.Ldfld, (FieldInfo)member); // [Member{i}] + } + } + + var paramGetter = GetType().GetMethod("Get", new Type[] { typeof(string) }).MakeGenericMethod(lastMemberAccess.Type); + + il.Emit(OpCodes.Ldarg_1); // [target] [DynamicParameters] + il.Emit(OpCodes.Ldstr, dynamicParamName); // [target] [DynamicParameters] [ParamName] + il.Emit(OpCodes.Callvirt, paramGetter); // [target] [value], it's already typed thanks to generic method + + // GET READY + var lastMember = lastMemberAccess.Member; + if (lastMember is PropertyInfo) + { + var set = ((PropertyInfo)lastMember).GetSetMethod(true); + il.Emit(OpCodes.Callvirt, set); // SET + } + else + { + il.Emit(OpCodes.Stfld, (FieldInfo)lastMember); // SET + } + + il.Emit(OpCodes.Ret); // GO + + setter = (Action)dm.CreateDelegate(typeof(Action)); + lock (cache) + { + cache[lookup] = setter; + } + + // Queue the preparation to be fired off when adding parameters to the DbCommand + MAKECALLBACK: + (outputCallbacks ?? (outputCallbacks = new List())).Add(() => + { + // Finally, prep the parameter and attach the callback to it + var targetMemberType = lastMemberAccess?.Type; + int sizeToSet = (!size.HasValue && targetMemberType == typeof(string)) ? DbString.DefaultLength : size ?? 0; + + if (parameters.TryGetValue(dynamicParamName, out ParamInfo parameter)) + { + parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput; + + if (parameter.AttachedParam.Size == 0) + { + parameter.Size = parameter.AttachedParam.Size = sizeToSet; + } + } + else + { + dbType = (!dbType.HasValue) +#pragma warning disable 618 + ? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out SqlMapper.ITypeHandler handler) +#pragma warning restore 618 + : dbType; + + // CameFromTemplate property would not apply here because this new param + // Still needs to be added to the command + Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet); + } + + parameter = parameters[dynamicParamName]; + parameter.OutputCallback = setter; + parameter.OutputTarget = target; + }); + + return this; + } + + private List outputCallbacks; + + void SqlMapper.IParameterCallbacks.OnCompleted() + { + foreach (var param in from p in parameters select p.Value) + { + param.OutputCallback?.Invoke(param.OutputTarget, this); + } + } + } +} diff --git a/Dapper/ExplicitConstructorAttribute.cs b/Dapper/ExplicitConstructorAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..f6c9cfa49dfa9c7698ae26a6ff0377b188e4b889 --- /dev/null +++ b/Dapper/ExplicitConstructorAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Dapper +{ + /// + /// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters + /// + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)] + public sealed class ExplicitConstructorAttribute : Attribute + { + } +} diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs new file mode 100644 index 0000000000000000000000000000000000000000..f98ba727442e53a7db186fb1a36426dd88f59068 --- /dev/null +++ b/Dapper/FeatureSupport.cs @@ -0,0 +1,36 @@ +using System; +using System.Data; + +namespace Dapper +{ + /// + /// Handles variances in features per DBMS + /// + internal class FeatureSupport + { + private static readonly FeatureSupport + Default = new FeatureSupport(false), + Postgres = new FeatureSupport(true); + + /// + /// Gets the feature set based on the passed connection + /// + /// The connection to get supported features for. + public static FeatureSupport Get(IDbConnection connection) + { + string name = connection?.GetType().Name; + if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres; + return Default; + } + + private FeatureSupport(bool arrays) + { + Arrays = arrays; + } + + /// + /// True if the db supports array columns e.g. Postgresql + /// + public bool Arrays { get; } + } +} diff --git a/Dapper/Properties/AssemblyInfo.cs b/Dapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..13a87fe6eed6e67e64fa078cc1b557465b05ccd4 --- /dev/null +++ b/Dapper/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("Dapper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dapper")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("b69ea382-d83f-481f-88b9-f0ac9dbce928")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Dapper/SimpleMemberMap.cs b/Dapper/SimpleMemberMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..b3b420bd7bde463d8d4c60341362dd8f11f01800 --- /dev/null +++ b/Dapper/SimpleMemberMap.cs @@ -0,0 +1,69 @@ +using System; +using System.Reflection; + +namespace Dapper +{ + /// + /// Represents simple member map for one of target parameter or property or field to source DataReader column + /// + internal sealed class SimpleMemberMap : SqlMapper.IMemberMap + { + /// + /// Creates instance for simple property mapping + /// + /// DataReader column name + /// Target property + public SimpleMemberMap(string columnName, PropertyInfo property) + { + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Property = property ?? throw new ArgumentNullException(nameof(property)); + } + + /// + /// Creates instance for simple field mapping + /// + /// DataReader column name + /// Target property + public SimpleMemberMap(string columnName, FieldInfo field) + { + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Field = field ?? throw new ArgumentNullException(nameof(field)); + } + + /// + /// Creates instance for simple constructor parameter mapping + /// + /// DataReader column name + /// Target constructor parameter + public SimpleMemberMap(string columnName, ParameterInfo parameter) + { + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter)); + } + + /// + /// DataReader column name + /// + public string ColumnName { get; } + + /// + /// Target member type + /// + public Type MemberType => Field?.FieldType ?? Property?.PropertyType ?? Parameter?.ParameterType; + + /// + /// Target property + /// + public PropertyInfo Property { get; } + + /// + /// Target field + /// + public FieldInfo Field { get; } + + /// + /// Target constructor parameter + /// + public ParameterInfo Parameter { get; } + } +} diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..2702f2dfc07a1ced372c837ccf8e0b658c75e85c --- /dev/null +++ b/Dapper/SqlDataRecordHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace Dapper +{ + internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler + { + public object Parse(Type destinationType, object value) + { + throw new NotSupportedException(); + } + + public void SetValue(IDbDataParameter parameter, object value) + { + SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); + } + } +} diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs new file mode 100644 index 0000000000000000000000000000000000000000..e5d89923912c65353b2a15ac286958b713135b9b --- /dev/null +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Dapper +{ + /// + /// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter + /// + internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter + { + private readonly IEnumerable data; + private readonly string typeName; + /// + /// Create a new instance of . + /// + /// The data records to convert into TVPs. + /// The parameter type name. + public SqlDataRecordListTVPParameter(IEnumerable data, string typeName) + { + this.data = data; + this.typeName = typeName; + } + + void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) + { + var param = command.CreateParameter(); + param.ParameterName = name; + Set(param, data, typeName); + command.Parameters.Add(param); + } + + internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) + { + parameter.Value = data != null && data.Any() ? data : null; + if (parameter is System.Data.SqlClient.SqlParameter sqlParam) + { + sqlParam.SqlDbType = SqlDbType.Structured; + sqlParam.TypeName = typeName; + } + } + } +} diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs new file mode 100644 index 0000000000000000000000000000000000000000..3259d236e37e2ac5f5f5f121a547dd8ad53bbd02 --- /dev/null +++ b/Dapper/SqlMapper.Async.cs @@ -0,0 +1,1228 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The command used to query on this connection. + /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => + QueryAsync(cnn, typeof(DapperRow), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The command used to query on this connection. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, typeof(DapperRow), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The command used to query on this connection. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The command used to query on this connection. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, typeof(DapperRow), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The command used to query on this connection. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); + + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The type of results to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of ; 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). + /// + public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return QueryAsync(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + } + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + } + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + } + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + } + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + } + + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A sequence of data of ; 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). + /// + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => + QueryAsync(cnn, typeof(T), command); + + /// + /// Execute a query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryAsync(cnn, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, typeof(T), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, typeof(T), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), command); + + private static Task ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken) + { + var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); + if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException)) + { // we can retry; this time it will have different flags + return cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); + } + return task; + } + + /// + /// Attempts to open a connection asynchronously, with a better error message for unsupported usages. + /// + private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cancel) + { + if (cnn is DbConnection dbConn) + { + return dbConn.OpenAsync(cancel); + } + else + { + throw new InvalidOperationException("Async operations require use of a DbConnection or an already-open IDbConnection"); + } + } + + /// + /// Attempts setup a on a , with a better error message for unsupported usages. + /// + private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action paramReader) + { + if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand) + { + return dbCommand; + } + else + { + throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand"); + } + } + + private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + var cancel = command.CancellationToken; + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + { + DbDataReader reader = null; + try + { + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); + + var tuple = info.Deserializer; + int hash = GetColumnHash(reader); + if (tuple.Func == null || tuple.Hash != hash) + { + if (reader.FieldCount == 0) + return Enumerable.Empty(); + 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) + { + var buffer = new List(); + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) + { + object val = func(reader); + if (val == null || val is T) + { + buffer.Add((T)val); + } + else + { + buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture)); + } + } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } + command.OnCompleted(); + return buffer; + } + else + { + // 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(reader, func, command.Parameters); + 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(); + } + } + } + + private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + var cancel = command.CancellationToken; + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + { + DbDataReader reader = null; + try + { + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0 + ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition + : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); + + T result = default(T); + if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) + { + 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; + + object val = func(reader); + if (val == null || val is T) + { + result = (T)val; + } + else + { + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } + } + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one + { + ThrowZeroRows(row); + } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } + return result; + } + finally + { + using (reader) { /* dispose if non-null */ } + if (wasClosed) cnn.Close(); + } + } + } + + /// + /// Execute a command asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The number of rows affected. + public static Task ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + + /// + /// Execute a command asynchronously using .NET 4.5 Task. + /// + /// The connection to execute on. + /// The command to execute on this connection. + /// The number of rows affected. + public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition command) + { + object param = command.Parameters; + IEnumerable multiExec = GetMultiExec(param); + if (multiExec != null) + { + return ExecuteMultiImplAsync(cnn, command, multiExec); + } + else + { + return ExecuteImplAsync(cnn, command, param); + } + } + + private struct AsyncExecState + { + public readonly DbCommand Command; + public readonly Task Task; + public AsyncExecState(DbCommand command, Task task) + { + Command = command; + Task = task; + } + } + + private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec) + { + bool isFirst = true; + int total = 0; + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + + CacheInfo info = null; + string masterSql = null; + if ((command.Flags & CommandFlags.Pipelined) != 0) + { + const int MAX_PENDING = 100; + var pending = new Queue(MAX_PENDING); + DbCommand cmd = null; + try + { + foreach (var obj in multiExec) + { + if (isFirst) + { + isFirst = false; + cmd = command.TrySetupAsyncCommand(cnn, null); + masterSql = cmd.CommandText; + var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); + info = GetCacheInfo(identity, obj, command.AddToCache); + } + else if (pending.Count >= MAX_PENDING) + { + var recycled = pending.Dequeue(); + total += await recycled.Task.ConfigureAwait(false); + cmd = recycled.Command; + cmd.CommandText = masterSql; // because we do magic replaces on "in" etc + cmd.Parameters.Clear(); // current code is Add-tastic + } + else + { + cmd = command.TrySetupAsyncCommand(cnn, null); + } + info.ParamReader(cmd, obj); + + var task = cmd.ExecuteNonQueryAsync(command.CancellationToken); + pending.Enqueue(new AsyncExecState(cmd, task)); + cmd = null; // note the using in the finally: this avoids a double-dispose + } + while (pending.Count != 0) + { + var pair = pending.Dequeue(); + using (pair.Command) { /* dispose commands */ } + total += await pair.Task.ConfigureAwait(false); + } + } + finally + { + // this only has interesting work to do if there are failures + using (cmd) { /* dispose commands */ } + while (pending.Count != 0) + { // dispose tasks even in failure + using (pending.Dequeue().Command) { /* dispose commands */ } + } + } + } + else + { + using (var cmd = command.TrySetupAsyncCommand(cnn, null)) + { + foreach (var obj in multiExec) + { + if (isFirst) + { + masterSql = cmd.CommandText; + isFirst = false; + var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); + info = GetCacheInfo(identity, obj, command.AddToCache); + } + 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 += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); + } + } + } + + command.OnCompleted(); + } + finally + { + if (wasClosed) cnn.Close(); + } + return total; + } + + private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) + { + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + { + try + { + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); + command.OnCompleted(); + return result; + } + finally + { + if (wasClosed) cnn.Close(); + } + } + } + + /// + /// Perform a asynchronous multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + /// + /// Perform a asynchronous multi-mapping query with 7 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + + /// + /// Perform an asynchronous multi-mapping query with 7 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); + + private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) + { + if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior + var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); + return command.Buffered ? results.ToList() : results; + } + } + finally + { + if (wasClosed) cnn.Close(); + } + } + + /// + /// Perform a asynchronous multi-mapping query with an arbitrary number of input types. + /// This returns a single type, combined from the raw types via . + /// + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// Array of types in the recordset. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)); + return MultiMapAsync(cnn, command, types, map, splitOn); + } + + private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn) + { + if (types.Length < 1) + { + throw new ArgumentException("you must provide at least one type to deserialize"); + } + + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) + { + var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); + return command.Buffered ? results.ToList() : results; + } + } + finally + { + if (wasClosed) cnn.Close(); + } + } + + private static IEnumerable ExecuteReaderSync(IDataReader reader, Func func, object parameters) + { + using (reader) + { + while (reader.Read()) + { + yield return (T)func(reader); + } + while (reader.NextResult()) { /* ignore subsequent result sets */ } + (parameters as IParameterCallbacks)?.OnCompleted(); + } + } + + /// + /// Execute a command that returns multiple result sets, and access each in turn. + /// + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + public static Task QueryMultipleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryMultipleAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); + + /// + /// Execute a command that returns multiple result sets, and access each in turn. + /// + /// The connection to query on. + /// The command to execute for this query. + public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); + + DbCommand cmd = null; + IDataReader reader = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false); + + var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, 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 + // 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(); + } + cmd?.Dispose(); + if (wasClosed) cnn.Close(); + throw; + } + } + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + /// + /// + /// + /// + /// + public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The command to execute. + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteReaderImplAsync(cnn, command, CommandBehavior.Default); + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The command to execute. + /// The flags for this reader. + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => + ExecuteReaderImplAsync(cnn, command, commandBehavior); + + private static async Task ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) + { + Action paramReader = GetParameterReader(cnn, ref command); + + DbCommand cmd = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + cmd = command.TrySetupAsyncCommand(cnn, paramReader); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); + wasClosed = false; + return reader; + } + finally + { + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + } + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The type to return. + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImplAsync(cnn, command); + + /// + /// Execute parameterized SQL that selects a single value + /// + /// The type to return. + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImplAsync(cnn, command); + + private static async Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) + { + Action paramReader = null; + object param = command.Parameters; + if (param != null) + { + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; + } + + DbCommand cmd = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + object result; + try + { + cmd = command.TrySetupAsyncCommand(cnn, paramReader); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false); + command.OnCompleted(); + } + finally + { + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + return Parse(result); + } + } +} diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..4955f714b3f579101299ec0ce6e97c71f7da7c4e --- /dev/null +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Data; +using System.Threading; + +namespace Dapper +{ + public static partial class SqlMapper + { + private class CacheInfo + { + public DeserializerState Deserializer { get; set; } + public Func[] OtherDeserializers { get; set; } + public Action ParamReader { get; set; } + private int hitCount; + public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } + public void RecordHit() { Interlocked.Increment(ref hitCount); } + } + } +} diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..4ddd6f03d08513b3711b9e78dcbe77b3b9fde717 --- /dev/null +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -0,0 +1,107 @@ +#if !NETSTANDARD1_3 // needs the component-model API +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Dapper +{ + public static partial class SqlMapper + { + [TypeDescriptionProvider(typeof(DapperRowTypeDescriptionProvider))] + private sealed partial class DapperRow + { + private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider + { + public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) + => new DapperRowTypeDescriptor(instance); + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) + => new DapperRowTypeDescriptor(instance); + } + + //// in theory we could implement this for zero-length results to bind; would require + //// additional changes, though, to capture a table even when no rows - so not currently provided + //internal sealed class DapperRowList : List, ITypedList + //{ + // private readonly DapperTable _table; + // public DapperRowList(DapperTable table) { _table = table; } + // PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) + // { + // if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty; + + // return DapperRowTypeDescriptor.GetProperties(_table); + // } + + // string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null; + //} + + private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor + { + private readonly DapperRow _row; + public DapperRowTypeDescriptor(object instance) + => _row = (DapperRow)instance; + + AttributeCollection ICustomTypeDescriptor.GetAttributes() + => AttributeCollection.Empty; + + string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName; + + string ICustomTypeDescriptor.GetComponentName() => null; + + private static readonly TypeConverter s_converter = new ExpandableObjectConverter(); + TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter; + + EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null; + + PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null; + + object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null; + + EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty; + + EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; + + internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); + internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary row = null) + { + string[] names = table?.FieldNames; + if (names == null || names.Length == 0) return PropertyDescriptorCollection.Empty; + var arr = new PropertyDescriptor[names.Length]; + for (int i = 0; i < arr.Length; i++) + { + var type = row != null && row.TryGetValue(names[i], out var value) && value != null + ? value.GetType() : typeof(object); + arr[i] = new RowBoundPropertyDescriptor(type, names[i], i); + } + return new PropertyDescriptorCollection(arr, true); + } + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row); + + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row); + + object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row; + } + + private sealed class RowBoundPropertyDescriptor : PropertyDescriptor + { + private readonly Type _type; + private readonly int _index; + public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name, null) + { + _type = type; + _index = index; + } + public override bool CanResetValue(object component) => true; + public override void ResetValue(object component) => ((DapperRow)component).Remove(_index); + public override bool IsReadOnly => false; + public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _); + public override Type ComponentType => typeof(DapperRow); + public override Type PropertyType => _type; + public override object GetValue(object component) + => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; + public override void SetValue(object component, object value) + => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value); + } + } + } +} +#endif diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs new file mode 100644 index 0000000000000000000000000000000000000000..a12188610bba18da1c365951a66c8f39e93ee678 --- /dev/null +++ b/Dapper/SqlMapper.DapperRow.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Dapper +{ + public static partial class SqlMapper + { + private sealed partial class DapperRow + : IDictionary + , IReadOnlyDictionary + { + private readonly DapperTable table; + private object[] values; + + public DapperRow(DapperTable table, object[] values) + { + this.table = table ?? throw new ArgumentNullException(nameof(table)); + this.values = values ?? throw new ArgumentNullException(nameof(values)); + } + + private sealed class DeadValue + { + public static readonly DeadValue Default = new DeadValue(); + private DeadValue() { /* hiding constructor */ } + } + + int ICollection>.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 key, out object value) + => TryGetValue(table.IndexOfName(key), out value); + + internal bool TryGetValue(int index, out object value) + { + 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 = GetStringBuilder().Append("{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('}').__ToStringRecycle(); + } + + public IEnumerator> 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(names[i], value); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #region Implementation of ICollection> + + void ICollection>.Add(KeyValuePair item) + { + IDictionary dic = this; + dic.Add(item.Key, item.Value); + } + + void ICollection>.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>.Contains(KeyValuePair item) + { + return TryGetValue(item.Key, out object value) && Equals(value, item.Value); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var kv in this) + { + array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + IDictionary dic = this; + return dic.Remove(item.Key); + } + + bool ICollection>.IsReadOnly => false; + #endregion + + #region Implementation of IDictionary + + bool IDictionary.ContainsKey(string key) + { + int index = table.IndexOfName(key); + if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; + return true; + } + + void IDictionary.Add(string key, object value) + { + SetValue(key, value, true); + } + + bool IDictionary.Remove(string key) + => Remove(table.IndexOfName(key)); + + internal bool Remove(int index) + { + if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; + values[index] = DeadValue.Default; + return true; + } + + object IDictionary.this[string key] + { + get { TryGetValue(key, out object 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(nameof(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", nameof(key)); + } + return SetValue(index, value); + } + internal object SetValue(int index, object value) + { + 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 IDictionary.Keys + { + get { return this.Select(kv => kv.Key).ToArray(); } + } + + ICollection IDictionary.Values + { + get { return this.Select(kv => kv.Value).ToArray(); } + } + + #endregion + + + #region Implementation of IReadOnlyDictionary + + + int IReadOnlyCollection>.Count + { + get + { + return values.Count(t => !(t is DeadValue)); + } + } + + bool IReadOnlyDictionary.ContainsKey(string key) + { + int index = table.IndexOfName(key); + return index >= 0 && index < values.Length && !(values[index] is DeadValue); + } + + object IReadOnlyDictionary.this[string key] + { + get { TryGetValue(key, out object val); return val; } + } + + IEnumerable IReadOnlyDictionary.Keys + { + get { return this.Select(kv => kv.Key); } + } + + IEnumerable IReadOnlyDictionary.Values + { + get { return this.Select(kv => kv.Value); } + } + + #endregion + } + } +} diff --git a/Dapper/SqlMapper.DapperRowMetaObject.cs b/Dapper/SqlMapper.DapperRowMetaObject.cs new file mode 100644 index 0000000000000000000000000000000000000000..69566ede5d0e69ad2fc5229dc2383d09cc1884df --- /dev/null +++ b/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +namespace Dapper +{ + public static partial class SqlMapper + { + private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider + { + System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( + System.Linq.Expressions.Expression parameter) + { + return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); + } + } + + private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject + { + private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); + private 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) + { + } + + private 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; + } + + static readonly string[] s_nixKeys = new string[0]; + public override IEnumerable GetDynamicMemberNames() + { + if(HasValue && Value is IDictionary lookup) return lookup.Keys; + return s_nixKeys; + } + } + } +} diff --git a/Dapper/SqlMapper.DapperTable.cs b/Dapper/SqlMapper.DapperTable.cs new file mode 100644 index 0000000000000000000000000000000000000000..cfd4fe717a601ec172922321fcc6fbe3d07e0137 --- /dev/null +++ b/Dapper/SqlMapper.DapperTable.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace Dapper +{ + public static partial class SqlMapper + { + private sealed class DapperTable + { + private string[] fieldNames; + private readonly Dictionary fieldNameLookup; + + internal string[] FieldNames => fieldNames; + + public DapperTable(string[] fieldNames) + { + this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames)); + + fieldNameLookup = new Dictionary(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) + { + return (name != null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; + } + + internal int AddField(string name) + { + if (name == null) throw new ArgumentNullException(nameof(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) => key != null && fieldNameLookup.ContainsKey(key); + + public int FieldCount => fieldNames.Length; + } + } +} diff --git a/Dapper/SqlMapper.DeserializerState.cs b/Dapper/SqlMapper.DeserializerState.cs new file mode 100644 index 0000000000000000000000000000000000000000..cbfba084a088143a2d20de8155701e6d67a7a0eb --- /dev/null +++ b/Dapper/SqlMapper.DeserializerState.cs @@ -0,0 +1,20 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + private struct DeserializerState + { + public readonly int Hash; + public readonly Func Func; + + public DeserializerState(int hash, Func func) + { + Hash = hash; + Func = func; + } + } + } +} diff --git a/Dapper/SqlMapper.DontMap.cs b/Dapper/SqlMapper.DontMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..d73ea9252135551fba56fb3f3dd6030fadb17db8 --- /dev/null +++ b/Dapper/SqlMapper.DontMap.cs @@ -0,0 +1,10 @@ +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Dummy type for excluding from multi-map + /// + private class DontMap { /* hiding constructor */ } + } +} diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs new file mode 100644 index 0000000000000000000000000000000000000000..460cc3990f527acf3faa6152dad80738f01bd163 --- /dev/null +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Dapper +{ + public static partial class SqlMapper + { + public partial class GridReader + { + private readonly CancellationToken cancel; + internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) + : this(command, reader, identity, dynamicParams, addToCache) + { + this.cancel = cancel; + } + + /// + /// Read the next grid of results, returned as a dynamic object + /// + /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + /// Whether to buffer the results. + public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(DapperRow), buffered); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.First); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.Single); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); + + /// + /// Read the next grid of results + /// + /// The type to read. + /// Whether to buffer the results. + /// is null. + public Task> ReadAsync(Type type, bool buffered = true) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadAsyncImpl(type, buffered); + } + + /// + /// Read an individual row of the next grid of results + /// + /// The type to read. + /// is null. + public Task ReadFirstAsync(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.First); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public Task ReadFirstOrDefaultAsync(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.FirstOrDefault); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public Task ReadSingleAsync(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.Single); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public Task ReadSingleOrDefaultAsync(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.SingleOrDefault); + } + + /// + /// Read the next grid of results. + /// + /// The type to read. + /// Whether the results should be buffered in memory. + public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(T), buffered); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(T), Row.First); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(T), Row.Single); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); + + private async Task NextResultAsync() + { + if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) + { + readCount++; + gridIndex++; + IsConsumed = false; + } + else + { + // happy path; close the reader cleanly - no + // need for "Cancel" etc + reader.Dispose(); + reader = null; + callbacks?.OnCompleted(); + Dispose(); + } + } + + private Task> ReadAsyncImpl(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 (IsConsumed) 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, addToCache); + 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; + } + IsConsumed = true; + if (buffered && reader is DbDataReader) + { + return ReadBufferedAsync(gridIndex, deserializer.Func); + } + else + { + var result = ReadDeferred(gridIndex, deserializer.Func, type); + if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario + return Task.FromResult(result); + } + } + + private Task ReadRowAsyncImpl(Type type, Row row) + { + if (reader is DbDataReader dbReader) return ReadRowAsyncImplViaDbReader(dbReader, type, row); + + // no async API available; use non-async and fake it + return Task.FromResult(ReadRow(type, row)); + } + + private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type type, Row row) + { + if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); + + IsConsumed = true; + T result = default(T); + if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) + { + var typedIdentity = identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + 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; + } + result = (T)deserializer.Func(reader); + if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent rows */ } + } + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one + { + ThrowZeroRows(row); + } + await NextResultAsync().ConfigureAwait(false); + return result; + } + + private async Task> ReadBufferedAsync(int index, Func deserializer) + { + try + { + var reader = (DbDataReader)this.reader; + var buffer = new List(); + while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) + { + buffer.Add((T)deserializer(reader)); + } + return buffer; + } + finally // finally so that First etc progresses things even when multiple rows + { + if (index == gridIndex) + { + await NextResultAsync().ConfigureAwait(false); + } + } + } + } + } +} diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs new file mode 100644 index 0000000000000000000000000000000000000000..8c8b4f7d003fbde219e7699ec67ba22b34a28278 --- /dev/null +++ b/Dapper/SqlMapper.GridReader.cs @@ -0,0 +1,445 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Globalization; +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// The grid reader provides interfaces for reading multiple result sets from a Dapper query + /// + public partial class GridReader : IDisposable + { + private IDataReader reader; + private readonly Identity identity; + private readonly bool addToCache; + + internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) + { + Command = command; + this.reader = reader; + this.identity = identity; + this.callbacks = callbacks; + this.addToCache = addToCache; + } + + /// + /// Read the next grid of results, returned as a dynamic object. + /// + /// Whether the results should be buffered in memory. + /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(DapperRow), buffered); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object. + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public dynamic ReadFirst() => ReadRow(typeof(DapperRow), Row.First); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object. + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public dynamic ReadFirstOrDefault() => ReadRow(typeof(DapperRow), Row.FirstOrDefault); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object. + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public dynamic ReadSingle() => ReadRow(typeof(DapperRow), Row.Single); + + /// + /// Read an individual row of the next grid of results, returned as a dynamic object. + /// + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public dynamic ReadSingleOrDefault() => ReadRow(typeof(DapperRow), Row.SingleOrDefault); + + /// + /// Read the next grid of results. + /// + /// The type to read. + /// Whether the results should be buffered in memory. + public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(T), buffered); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public T ReadFirst() => ReadRow(typeof(T), Row.First); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public T ReadFirstOrDefault() => ReadRow(typeof(T), Row.FirstOrDefault); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public T ReadSingle() => ReadRow(typeof(T), Row.Single); + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + public T ReadSingleOrDefault() => ReadRow(typeof(T), Row.SingleOrDefault); + + /// + /// Read the next grid of results. + /// + /// The type to read. + /// Whether to buffer the results. + /// is null. + public IEnumerable Read(Type type, bool buffered = true) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadImpl(type, buffered); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public object ReadFirst(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRow(type, Row.First); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public object ReadFirstOrDefault(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRow(type, Row.FirstOrDefault); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public object ReadSingle(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRow(type, Row.Single); + } + + /// + /// Read an individual row of the next grid of results. + /// + /// The type to read. + /// is null. + public object ReadSingleOrDefault(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return ReadRow(type, Row.SingleOrDefault); + } + + private IEnumerable ReadImpl(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 (IsConsumed) 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, addToCache); + 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; + } + IsConsumed = true; + var result = ReadDeferred(gridIndex, deserializer.Func, type); + return buffered ? result.ToList() : result; + } + + private T ReadRow(Type type, Row row) + { + if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); + IsConsumed = true; + + T result = default(T); + if (reader.Read() && reader.FieldCount != 0) + { + var typedIdentity = identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + 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; + } + object val = deserializer.Func(reader); + if (val == null || val is T) + { + result = (T)val; + } + else + { + var convertToType = Nullable.GetUnderlyingType(type) ?? type; + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); + while (reader.Read()) { /* ignore subsequent rows */ } + } + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one + { + ThrowZeroRows(row); + } + NextResult(); + return result; + } + + private IEnumerable MultiReadInternal(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); + + IsConsumed = true; + + try + { + foreach (var r in MultiMapImpl(null, default(CommandDefinition), func, splitOn, reader, identity, false)) + { + yield return r; + } + } + finally + { + NextResult(); + } + } + + private IEnumerable MultiReadInternal(Type[] types, Func map, string splitOn) + { + var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex); + try + { + foreach (var r in MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, false)) + { + yield return r; + } + } + finally + { + NextResult(); + } + } + + /// + /// Read multiple objects from a single record set on the grid. + /// + /// The first type in the record set. + /// The second type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid. + /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid + /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid + /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid + /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The sixth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid + /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The sixth type in the record set. + /// The seventh type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(func, splitOn); + return buffered ? result.ToList() : result; + } + + /// + /// Read multiple objects from a single record set on the grid + /// + /// The type to return from the record set. + /// The types to read from the result set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. + public IEnumerable Read(Type[] types, Func map, string splitOn = "id", bool buffered = true) + { + var result = MultiReadInternal(types, map, splitOn); + return buffered ? result.ToList() : result; + } + + private IEnumerable ReadDeferred(int index, Func deserializer, Type effectiveType) + { + try + { + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + while (index == gridIndex && reader.Read()) + { + object val = deserializer(reader); + if (val == null || val is T) + { + yield return (T)val; + } + else + { + yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + } + } + finally // finally so that First etc progresses things even when multiple rows + { + if (index == gridIndex) + { + NextResult(); + } + } + } + + private int gridIndex, readCount; + private readonly IParameterCallbacks callbacks; + + /// + /// Has the underlying reader been consumed? + /// + public bool IsConsumed { get; private set; } + + /// + /// The command associated with the reader + /// + public IDbCommand Command { get; set; } + + private void NextResult() + { + if (reader.NextResult()) + { + readCount++; + gridIndex++; + IsConsumed = false; + } + else + { + // happy path; close the reader cleanly - no + // need for "Cancel" etc + reader.Dispose(); + reader = null; + callbacks?.OnCompleted(); + Dispose(); + } + } + + /// + /// Dispose the grid, closing and disposing both the underlying reader and command. + /// + public void Dispose() + { + if (reader != null) + { + if (!reader.IsClosed) Command?.Cancel(); + reader.Dispose(); + reader = null; + } + if (Command != null) + { + Command.Dispose(); + Command = null; + } + } + } + } +} diff --git a/Dapper/SqlMapper.ICustomQueryParameter.cs b/Dapper/SqlMapper.ICustomQueryParameter.cs new file mode 100644 index 0000000000000000000000000000000000000000..d06ae8ec4367305bb24313003e3dec63fbfe5412 --- /dev/null +++ b/Dapper/SqlMapper.ICustomQueryParameter.cs @@ -0,0 +1,20 @@ +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Implement this interface to pass an arbitrary db specific parameter to Dapper + /// + public interface ICustomQueryParameter + { + /// + /// Add the parameter needed to the command before it executes + /// + /// The raw command prior to execution + /// Parameter name + void AddParameter(IDbCommand command, string name); + } + } +} diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs new file mode 100644 index 0000000000000000000000000000000000000000..e8ca4877917ad1321c2cb79f2503f60b574d97ca --- /dev/null +++ b/Dapper/SqlMapper.IDataReader.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Parses a data reader to a sequence of data of the supplied type. Used for deserializing a reader without a connection, etc. + /// + /// The type to parse from the . + /// The data reader to parse results from. + public static IEnumerable Parse(this IDataReader reader) + { + if (reader.Read()) + { + var effectiveType = typeof(T); + var deser = GetDeserializer(effectiveType, reader, 0, -1, false); + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + do + { + object val = deser(reader); + if (val == null || val is T) + { + yield return (T)val; + } + else + { + yield return (T)Convert.ChangeType(val, convertToType, System.Globalization.CultureInfo.InvariantCulture); + } + } while (reader.Read()); + } + } + + /// + /// Parses a data reader to a sequence of data of the supplied type (as object). Used for deserializing a reader without a connection, etc. + /// + /// The data reader to parse results from. + /// The type to parse from the . + public static IEnumerable Parse(this IDataReader reader, Type type) + { + if (reader.Read()) + { + var deser = GetDeserializer(type, reader, 0, -1, false); + do + { + yield return deser(reader); + } while (reader.Read()); + } + } + + /// + /// Parses a data reader to a sequence of dynamic. Used for deserializing a reader without a connection, etc. + /// + /// The data reader to parse results from. + public static IEnumerable Parse(this IDataReader reader) + { + if (reader.Read()) + { + var deser = GetDapperRowDeserializer(reader, 0, -1, false); + do + { + yield return deser(reader); + } while (reader.Read()); + } + } + + /// + /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. + /// You could return a collection of the base type but have each more specific. + /// + /// The data reader to get the parser for the current row from + /// The type to get the parser for + /// The start column index of the object (default 0) + /// The length of columns to read (default -1 = all fields following startIndex) + /// Return null if we can't find the first column? (default false) + /// A parser for this specific object from this row. + public static Func GetRowParser(this IDataReader reader, Type type, + int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) + { + return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing); + } + + /// + /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. + /// You could return a collection of the base type but have each more specific. + /// + /// The type of results to return. + /// The data reader to get the parser for the current row from. + /// The type to get the parser for. + /// The start column index of the object (default: 0). + /// The length of columns to read (default: -1 = all fields following startIndex). + /// Return null if we can't find the first column? (default: false). + /// A parser for this specific object from this row. + /// + /// var result = new List<BaseType>(); + /// using (var reader = connection.ExecuteReader(@" + /// select 'abc' as Name, 1 as Type, 3.0 as Value + /// union all + /// select 'def' as Name, 2 as Type, 4.0 as Value")) + /// { + /// if (reader.Read()) + /// { + /// var toFoo = reader.GetRowParser<BaseType>(typeof(Foo)); + /// var toBar = reader.GetRowParser<BaseType>(typeof(Bar)); + /// var col = reader.GetOrdinal("Type"); + /// do + /// { + /// switch (reader.GetInt32(col)) + /// { + /// case 1: + /// result.Add(toFoo(reader)); + /// break; + /// case 2: + /// result.Add(toBar(reader)); + /// break; + /// } + /// } while (reader.Read()); + /// } + /// } + /// + /// abstract class BaseType + /// { + /// public abstract int Type { get; } + /// } + /// class Foo : BaseType + /// { + /// public string Name { get; set; } + /// public override int Type => 1; + /// } + /// class Bar : BaseType + /// { + /// public float Value { get; set; } + /// public override int Type => 2; + /// } + /// + public static Func GetRowParser(this IDataReader reader, Type concreteType = null, + int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) + { + concreteType = concreteType ?? typeof(T); + var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); + if (concreteType.IsValueType()) + { + return _ => (T)func(_); + } + else + { + return (Func)(Delegate)func; + } + } + } +} diff --git a/Dapper/SqlMapper.IDynamicParameters.cs b/Dapper/SqlMapper.IDynamicParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..0b0a650b310afb988daefbaf5b9e486d964d0a0e --- /dev/null +++ b/Dapper/SqlMapper.IDynamicParameters.cs @@ -0,0 +1,20 @@ +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Implement this interface to pass an arbitrary db specific set of parameters to Dapper + /// + public interface IDynamicParameters + { + /// + /// Add all the parameters needed to the command just before it executes + /// + /// The raw command prior to execution + /// Information about the query + void AddParameters(IDbCommand command, Identity identity); + } + } +} diff --git a/Dapper/SqlMapper.IMemberMap.cs b/Dapper/SqlMapper.IMemberMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..77a857ce7172551bbf1fa0e03c21df033d8ead2c --- /dev/null +++ b/Dapper/SqlMapper.IMemberMap.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Implements this interface to provide custom member mapping + /// + public interface IMemberMap + { + /// + /// Source DataReader column name + /// + string ColumnName { get; } + + /// + /// Target member type + /// + Type MemberType { get; } + + /// + /// Target property + /// + PropertyInfo Property { get; } + + /// + /// Target field + /// + FieldInfo Field { get; } + + /// + /// Target constructor parameter + /// + ParameterInfo Parameter { get; } + } + } +} diff --git a/Dapper/SqlMapper.IParameterCallbacks.cs b/Dapper/SqlMapper.IParameterCallbacks.cs new file mode 100644 index 0000000000000000000000000000000000000000..b6a69ea9dadc186800ac49ae687d952c5017271d --- /dev/null +++ b/Dapper/SqlMapper.IParameterCallbacks.cs @@ -0,0 +1,16 @@ +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Extends IDynamicParameters with facilities for executing callbacks after commands have completed + /// + public interface IParameterCallbacks : IDynamicParameters + { + /// + /// Invoked when the command has executed + /// + void OnCompleted(); + } + } +} diff --git a/Dapper/SqlMapper.IParameterLookup.cs b/Dapper/SqlMapper.IParameterLookup.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0b128ba1381274e2c1d906dc49a92b7e2286bc2 --- /dev/null +++ b/Dapper/SqlMapper.IParameterLookup.cs @@ -0,0 +1,17 @@ +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Extends IDynamicParameters providing by-name lookup of parameter values + /// + public interface IParameterLookup : IDynamicParameters + { + /// + /// Get the value of the specified parameter (return null if not found) + /// + /// The name of the parameter to get. + object this[string name] { get; } + } + } +} diff --git a/Dapper/SqlMapper.ITypeHandler.cs b/Dapper/SqlMapper.ITypeHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..2404a94f4e1bcacfdc6f8c89e4b3cd2f3e006fb5 --- /dev/null +++ b/Dapper/SqlMapper.ITypeHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Implement this interface to perform custom type-based parameter handling and value parsing + /// + public interface ITypeHandler + { + /// + /// Assign the value of a parameter before a command executes + /// + /// The parameter to configure + /// Parameter value + void SetValue(IDbDataParameter parameter, object value); + + /// + /// Parse a database value back to a typed value + /// + /// The value from the database + /// The type to parse to + /// The typed value + object Parse(Type destinationType, object value); + } + } +} diff --git a/Dapper/SqlMapper.ITypeMap.cs b/Dapper/SqlMapper.ITypeMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..b178c60a5e8859609b53709a7fd83cb78d8e80ef --- /dev/null +++ b/Dapper/SqlMapper.ITypeMap.cs @@ -0,0 +1,46 @@ +using System; +using System.Reflection; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Implement this interface to change default mapping of reader columns to type members + /// + public interface ITypeMap + { + /// + /// Finds best constructor + /// + /// DataReader column names + /// DataReader column types + /// Matching constructor or default one + ConstructorInfo FindConstructor(string[] names, Type[] types); + + /// + /// Returns a constructor which should *always* be used. + /// + /// Parameters will be default values, nulls for reference types and zero'd for value types. + /// + /// Use this class to force object creation away from parameterless constructors you don't control. + /// + ConstructorInfo FindExplicitConstructor(); + + /// + /// Gets mapping for constructor parameter + /// + /// Constructor to resolve + /// DataReader column name + /// Mapping implementation + IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName); + + /// + /// Gets member mapping for column + /// + /// DataReader column name + /// Mapping implementation + IMemberMap GetMember(string columnName); + } + } +} diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs new file mode 100644 index 0000000000000000000000000000000000000000..3fcdbb7565e86a03b014f7a2f5e33cee3047d54e --- /dev/null +++ b/Dapper/SqlMapper.Identity.cs @@ -0,0 +1,121 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Identity of a cached query in Dapper, used for extensibility. + /// + public class Identity : IEquatable + { + internal Identity ForGrid(Type primaryType, int gridIndex) => + new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); + + internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => + new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); + + /// + /// Create an identity for use with DynamicParameters, internal use only. + /// + /// The parameters type to create an for. + /// + public Identity ForDynamicParameters(Type type) => + 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) { /* base call */ } + + 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?.GetHashCode() ?? 0); + hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0); + if (otherTypes != null) + { + foreach (var t in otherTypes) + { + hashCode = (hashCode * 23) + (t?.GetHashCode() ?? 0); + } + } + hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); + hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); + } + } + + /// + /// Whether this equals another. + /// + /// The other to compare to. + public override bool Equals(object obj) => Equals(obj as Identity); + + /// + /// The raw SQL command. + /// + public readonly string sql; + + /// + /// The SQL command type. + /// + public readonly CommandType? commandType; + + /// + /// The hash code of this Identity. + /// + public readonly int hashCode; + + /// + /// The grid index (position in the reader) of this Identity. + /// + public readonly int gridIndex; + + /// + /// This of this Identity. + /// + public readonly Type type; + + /// + /// The connection string for this Identity. + /// + public readonly string connectionString; + + /// + /// The type of the parameters object for this Identity. + /// + public readonly Type parametersType; + + /// + /// Gets the hash code for this identity. + /// + /// + public override int GetHashCode() => hashCode; + + /// + /// Compare 2 Identity objects + /// + /// The other object to compare. + /// Whether the two are equal + public bool Equals(Identity other) + { + return other != null + && gridIndex == other.gridIndex + && type == other.type + && sql == other.sql + && commandType == other.commandType + && connectionStringComparer.Equals(connectionString, other.connectionString) + && parametersType == other.parametersType; + } + } + } +} diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f54c3bdeacdc4839fbda98b71f84f5a74eda84c --- /dev/null +++ b/Dapper/SqlMapper.Link.cs @@ -0,0 +1,61 @@ +using System.Threading; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// 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. + /// + /// The type to cache. + /// The value type of the cache. + internal class Link where TKey : class + { + public static bool TryGet(Link 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 head, TKey key, ref TValue value) + { + bool tryAgain; + do + { + var snapshot = Interlocked.CompareExchange(ref head, null, null); + if (TryGet(snapshot, key, out TValue found)) + { // existing match; report the existing value instead + value = found; + return false; + } + var newNode = new Link(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 tail) + { + Key = key; + Value = value; + Tail = tail; + } + + public TKey Key { get; } + public TValue Value { get; } + public Link Tail { get; } + } + } +} diff --git a/Dapper/SqlMapper.LiteralToken.cs b/Dapper/SqlMapper.LiteralToken.cs new file mode 100644 index 0000000000000000000000000000000000000000..c174e8f1942c4b78ff7c56cd791cf2e3c7dbe4bb --- /dev/null +++ b/Dapper/SqlMapper.LiteralToken.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql + /// + internal struct LiteralToken + { + /// + /// The text in the original command that should be replaced + /// + public string Token { get; } + + /// + /// The name of the member referred to by the token + /// + public string Member { get; } + + internal LiteralToken(string token, string member) + { + Token = token; + Member = member; + } + + internal static readonly IList None = new LiteralToken[0]; + } + } +} diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb3f8aaea425772f8f5d84ee833924c119d44ac5 --- /dev/null +++ b/Dapper/SqlMapper.Settings.cs @@ -0,0 +1,98 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Permits specifying certain SqlMapper values globally. + /// + public static class Settings + { + // disable single result by default; prevents errors AFTER the select being detected properly + private const CommandBehavior DefaultAllowedCommandBehaviors = ~CommandBehavior.SingleResult; + internal static CommandBehavior AllowedCommandBehaviors { get; private set; } = DefaultAllowedCommandBehaviors; + private static void SetAllowedCommandBehaviors(CommandBehavior behavior, bool enabled) + { + if (enabled) AllowedCommandBehaviors |= behavior; + else AllowedCommandBehaviors &= ~behavior; + } + /// + /// Gets or sets whether Dapper should use the CommandBehavior.SingleResult optimization + /// + /// Note that a consequence of enabling this option is that errors that happen after the first select may not be reported + public static bool UseSingleResultOptimization + { + get { return (AllowedCommandBehaviors & CommandBehavior.SingleResult) != 0; } + set { SetAllowedCommandBehaviors(CommandBehavior.SingleResult, value); } + } + /// + /// Gets or sets whether Dapper should use the CommandBehavior.SingleRow optimization + /// + /// Note that on some DB providers this optimization can have adverse performance impact + public static bool UseSingleRowOptimization + { + get { return (AllowedCommandBehaviors & CommandBehavior.SingleRow) != 0; } + set { SetAllowedCommandBehaviors(CommandBehavior.SingleRow, value); } + } + + internal static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex) + { + if (AllowedCommandBehaviors == DefaultAllowedCommandBehaviors + && (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0) + { + if (ex.Message.Contains(nameof(CommandBehavior.SingleResult)) + || ex.Message.Contains(nameof(CommandBehavior.SingleRow))) + { // some providers just just allow these, so: try again without them and stop issuing them + SetAllowedCommandBehaviors(CommandBehavior.SingleResult | CommandBehavior.SingleRow, false); + return true; + } + } + return false; + } + + static Settings() + { + SetDefaults(); + } + + /// + /// Resets all Settings to their default values + /// + public static void SetDefaults() + { + CommandTimeout = null; + ApplyNullValues = false; + } + + /// + /// Specifies the default Command Timeout for all Queries + /// + public static int? CommandTimeout { get; set; } + + /// + /// Indicates whether nulls in data are silently ignored (default) vs actively applied and assigned to members + /// + public static bool ApplyNullValues { get; set; } + + /// + /// Should list expansions be padded with null-valued parameters, to prevent query-plan saturation? For example, + /// an 'in @foo' expansion with 7, 8 or 9 values will be sent as a list of 10 values, with 3, 2 or 1 of them null. + /// The padding size is relative to the size of the list; "next 10" under 150, "next 50" under 500, + /// "next 100" under 1500, etc. + /// + /// + /// Caution: this should be treated with care if your DB provider (or the specific configuration) allows for null + /// equality (aka "ansi nulls off"), as this may change the intent of your query; as such, this is disabled by + /// default and must be enabled. + /// + public static bool PadListExpansions { get; set; } + /// + /// If set (non-negative), when performing in-list expansions of integer types ("where id in @ids", etc), switch to a string_split based + /// operation if there are more than this many elements. Note that this feautre requires SQL Server 2016 / compatibility level 130 (or above). + /// + public static int InListStringSplitCount { get; set; } = -1; + } + } +} diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs new file mode 100644 index 0000000000000000000000000000000000000000..12b1c17cbfb1395c840ae3cab74fa6efb3b561fc --- /dev/null +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -0,0 +1,163 @@ +using System; +using System.Data; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Dapper +{ + public static partial class SqlMapper + { + private class TypeDeserializerCache + { + private TypeDeserializerCache(Type type) + { + this.type = type; + } + + private static readonly Hashtable byType = new Hashtable(); + private readonly Type type; + internal static void Purge(Type type) + { + lock (byType) + { + byType.Remove(type); + } + } + + internal static void Purge() + { + lock (byType) + { + byType.Clear(); + } + } + + internal static Func GetReader(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + { + var found = (TypeDeserializerCache)byType[type]; + if (found == null) + { + lock (byType) + { + found = (TypeDeserializerCache)byType[type]; + if (found == null) + { + byType[type] = found = new TypeDeserializerCache(type); + } + } + } + return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); + } + + private readonly Dictionary> readers = new Dictionary>(); + + private struct DeserializerKey : IEquatable + { + private readonly int startBound, length; + private readonly bool returnNullIfFirstMissing; + private readonly IDataReader reader; + private readonly string[] names; + private readonly Type[] types; + private readonly int hashCode; + + public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, IDataReader reader, bool copyDown) + { + this.hashCode = hashCode; + this.startBound = startBound; + this.length = length; + this.returnNullIfFirstMissing = returnNullIfFirstMissing; + + if (copyDown) + { + this.reader = null; + names = new string[length]; + types = new Type[length]; + int index = startBound; + for (int i = 0; i < length; i++) + { + names[i] = reader.GetName(index); + types[i] = reader.GetFieldType(index++); + } + } + else + { + this.reader = reader; + names = null; + types = null; + } + } + + public override int GetHashCode() => hashCode; + + public override string ToString() + { // only used in the debugger + if (names != null) + { + return string.Join(", ", names); + } + if (reader != null) + { + var sb = new StringBuilder(); + int index = startBound; + for (int i = 0; i < length; i++) + { + if (i != 0) sb.Append(", "); + sb.Append(reader.GetName(index++)); + } + return sb.ToString(); + } + return base.ToString(); + } + + public override bool Equals(object obj) + { + return obj is DeserializerKey && Equals((DeserializerKey)obj); + } + + public bool Equals(DeserializerKey other) + { + if (hashCode != other.hashCode + || startBound != other.startBound + || length != other.length + || returnNullIfFirstMissing != other.returnNullIfFirstMissing) + { + return false; // clearly different + } + for (int i = 0; i < length; i++) + { + if ((names?[i] ?? reader?.GetName(startBound + i)) != (other.names?[i] ?? other.reader?.GetName(startBound + i)) + || + (types?[i] ?? reader?.GetFieldType(startBound + i)) != (other.types?[i] ?? other.reader?.GetFieldType(startBound + i)) + ) + { + return false; // different column name or type + } + } + return true; + } + } + + private Func GetReader(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + { + if (length < 0) length = reader.FieldCount - startBound; + int hash = GetColumnHash(reader, startBound, length); + if (returnNullIfFirstMissing) hash *= -27; + // get a cheap key first: false means don't copy the values down + var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false); + Func deser; + lock (readers) + { + if (readers.TryGetValue(key, out deser)) return deser; + } + deser = GetTypeDeserializerImpl(type, reader, startBound, length, returnNullIfFirstMissing); + // get a more expensive key: true means copy the values down so it can be used as a key later + key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, true); + lock (readers) + { + return readers[key] = deser; + } + } + } + } +} diff --git a/Dapper/SqlMapper.TypeHandler.cs b/Dapper/SqlMapper.TypeHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..efb402aab5f01c74f45f019fe260606e6446884c --- /dev/null +++ b/Dapper/SqlMapper.TypeHandler.cs @@ -0,0 +1,86 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Base-class for simple type-handlers + /// + /// This this handler is for. + public abstract class TypeHandler : ITypeHandler + { + /// + /// Assign the value of a parameter before a command executes + /// + /// The parameter to configure + /// Parameter value + public abstract void SetValue(IDbDataParameter parameter, T value); + + /// + /// Parse a database value back to a typed value + /// + /// The value from the database + /// The typed value + public abstract T Parse(object value); + + void ITypeHandler.SetValue(IDbDataParameter parameter, object value) + { + if (value is DBNull) + { + parameter.Value = value; + } + else + { + SetValue(parameter, (T)value); + } + } + + object ITypeHandler.Parse(Type destinationType, object value) + { + return Parse(value); + } + } + + /// + /// Base-class for simple type-handlers that are based around strings + /// + /// This this handler is for. + public abstract class StringTypeHandler : TypeHandler + { + /// + /// Parse a string into the expected type (the string will never be null) + /// + /// The string to parse. + protected abstract T Parse(string xml); + + /// + /// Format an instace into a string (the instance will never be null) + /// + /// The string to format. + protected abstract string Format(T xml); + + /// + /// Assign the value of a parameter before a command executes + /// + /// The parameter to configure + /// Parameter value + public override void SetValue(IDbDataParameter parameter, T value) + { + parameter.Value = value == null ? (object)DBNull.Value : Format(value); + } + + /// + /// Parse a database value back to a typed value + /// + /// The value from the database + /// The typed value + public override T Parse(object value) + { + if (value == null || value is DBNull) return default(T); + return Parse((string)value); + } + } + } +} diff --git a/Dapper/SqlMapper.TypeHandlerCache.cs b/Dapper/SqlMapper.TypeHandlerCache.cs new file mode 100644 index 0000000000000000000000000000000000000000..e1ea47c1c9ae206d270dbd30ba52ab608daaae75 --- /dev/null +++ b/Dapper/SqlMapper.TypeHandlerCache.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { + /// + /// Not intended for direct usage + /// + /// The type to have a cache for. + [Obsolete(ObsoleteInternalUsageOnly, false)] +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + public static class TypeHandlerCache + { + /// + /// Not intended for direct usage. + /// + /// The object to parse. + [Obsolete(ObsoleteInternalUsageOnly, true)] + public static T Parse(object value) => (T)handler.Parse(typeof(T), value); + + /// + /// Not intended for direct usage. + /// + /// The parameter to set a value for. + /// The value to set. + [Obsolete(ObsoleteInternalUsageOnly, true)] + public static void SetValue(IDbDataParameter parameter, object value) => handler.SetValue(parameter, value); + + internal static void SetHandler(ITypeHandler handler) + { +#pragma warning disable 618 + TypeHandlerCache.handler = handler; +#pragma warning restore 618 + } + + private static ITypeHandler handler; + } + } +} diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs new file mode 100644 index 0000000000000000000000000000000000000000..e6ff7180e16d712b04bd64790e1a16401355fd9c --- /dev/null +++ b/Dapper/SqlMapper.cs @@ -0,0 +1,3722 @@ +/* + License: http://www.apache.org/licenses/LICENSE-2.0 + Home page: https://github.com/StackExchange/dapper-dot-net + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; +using System.Xml.Linq; + +#if NETSTANDARD1_3 +using DataException = System.InvalidOperationException; +#endif + +namespace Dapper +{ + /// + /// Dapper, a light weight object mapper for ADO.NET + /// + public static partial class SqlMapper + { + private class PropertyInfoByNameComparer : IComparer + { + public int Compare(PropertyInfo x, PropertyInfo y) => string.CompareOrdinal(x.Name, y.Name); + } + private static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1) + { + unchecked + { + int max = length < 0 ? reader.FieldCount : startBound + length; + int hash = (-37 * startBound) + max; + for (int i = startBound; i < max; i++) + { + object tmp = reader.GetName(i); + hash = (-79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0))) + (reader.GetFieldType(i)?.GetHashCode() ?? 0); + } + return hash; + } + } + + /// + /// Called if the query cache is purged via PurgeQueryCache + /// + public static event EventHandler QueryCachePurged; + private static void OnQueryCachePurged() + { + var handler = QueryCachePurged; + handler?.Invoke(null, EventArgs.Empty); + } + + private static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary(); + 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) + { + _queryCache.TryRemove(pair.Key, out CacheInfo 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; + } + + /// + /// Purge the query cache + /// + public static void PurgeQueryCache() + { + _queryCache.Clear(); + TypeDeserializerCache.Purge(); + OnQueryCachePurged(); + } + + private static void PurgeQueryCacheByType(Type type) + { + foreach (var entry in _queryCache) + { + if (entry.Key.type == type) + _queryCache.TryRemove(entry.Key, out CacheInfo cache); + } + TypeDeserializerCache.Purge(type); + } + + /// + /// Return a count of all the cached queries by Dapper + /// + /// + public static int GetCachedSQLCount() + { + return _queryCache.Count; + } + + /// + /// Return a list of all the queries cached by Dapper + /// + /// + /// + public static IEnumerable> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) + { + var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); + return (ignoreHitCountAbove < int.MaxValue) + ? data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove) + : data; + } + + /// + /// Deep diagnostics only: find any hash collisions in the cache + /// + /// + public static IEnumerable> GetHashCollissions() + { + var counts = new Dictionary(); + foreach (var key in _queryCache.Keys) + { + if (!counts.TryGetValue(key.hashCode, out int 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); + } + + private static Dictionary typeMap; + + static SqlMapper() + { + typeMap = new Dictionary + { + [typeof(byte)] = DbType.Byte, + [typeof(sbyte)] = DbType.SByte, + [typeof(short)] = DbType.Int16, + [typeof(ushort)] = DbType.UInt16, + [typeof(int)] = DbType.Int32, + [typeof(uint)] = DbType.UInt32, + [typeof(long)] = DbType.Int64, + [typeof(ulong)] = DbType.UInt64, + [typeof(float)] = DbType.Single, + [typeof(double)] = DbType.Double, + [typeof(decimal)] = DbType.Decimal, + [typeof(bool)] = DbType.Boolean, + [typeof(string)] = DbType.String, + [typeof(char)] = DbType.StringFixedLength, + [typeof(Guid)] = DbType.Guid, + [typeof(DateTime)] = DbType.DateTime, + [typeof(DateTimeOffset)] = DbType.DateTimeOffset, + [typeof(TimeSpan)] = DbType.Time, + [typeof(byte[])] = DbType.Binary, + [typeof(byte?)] = DbType.Byte, + [typeof(sbyte?)] = DbType.SByte, + [typeof(short?)] = DbType.Int16, + [typeof(ushort?)] = DbType.UInt16, + [typeof(int?)] = DbType.Int32, + [typeof(uint?)] = DbType.UInt32, + [typeof(long?)] = DbType.Int64, + [typeof(ulong?)] = DbType.UInt64, + [typeof(float?)] = DbType.Single, + [typeof(double?)] = DbType.Double, + [typeof(decimal?)] = DbType.Decimal, + [typeof(bool?)] = DbType.Boolean, + [typeof(char?)] = DbType.StringFixedLength, + [typeof(Guid?)] = DbType.Guid, + [typeof(DateTime?)] = DbType.DateTime, + [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, + [typeof(TimeSpan?)] = DbType.Time, + [typeof(object)] = DbType.Object + }; + ResetTypeHandlers(false); + } + + /// + /// Clear the registered type handlers. + /// + public static void ResetTypeHandlers() => ResetTypeHandlers(true); + + private static void ResetTypeHandlers(bool clone) + { + typeHandlers = new Dictionary(); +#if !NETSTANDARD1_3 + AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); +#endif + try + { + AddSqlDataRecordsTypeHandler(clone); + } + catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } + AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); + AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); + AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AddSqlDataRecordsTypeHandler(bool clone) + { + AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); + } + + /// + /// Configure the specified type to be mapped to a given db-type. + /// + /// The type to map from. + /// The database type to map to. + public static void AddTypeMap(Type type, DbType dbType) + { + // use clone, mutate, replace to avoid threading issues + var snapshot = typeMap; + + if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do + + typeMap = new Dictionary(snapshot) { [type] = dbType }; + } + + /// + /// Removes the specified type from the Type/DbType mapping table. + /// + /// The type to remove from the current map. + public static void RemoveTypeMap(Type type) + { + // use clone, mutate, replace to avoid threading issues + var snapshot = typeMap; + + if (!snapshot.ContainsKey(type)) return; // nothing to do + + var newCopy = new Dictionary(snapshot); + newCopy.Remove(type); + + typeMap = newCopy; + } + + /// + /// Configure the specified type to be processed by a custom handler. + /// + /// The type to handle. + /// The handler to process the . + public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerImpl(type, handler, true); + + internal static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); + + /// + /// Configure the specified type to be processed by a custom handler. + /// + /// The type to handle. + /// The handler to process the . + /// Whether to clone the current type handler map. + public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clone) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + Type secondary = null; + if (type.IsValueType()) + { + var underlying = Nullable.GetUnderlyingType(type); + if (underlying == null) + { + secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable + // type is already the T + } + else + { + secondary = type; // the Nullable + type = underlying; // the T + } + } + + var snapshot = typeHandlers; + if (snapshot.TryGetValue(type, out ITypeHandler oldValue) && handler == oldValue) return; // nothing to do + + var newCopy = clone ? new Dictionary(snapshot) : snapshot; + +#pragma warning disable 618 + typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); + if (secondary != null) + { + typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); + } +#pragma warning restore 618 + if (handler == null) + { + newCopy.Remove(type); + if (secondary != null) newCopy.Remove(secondary); + } + else + { + newCopy[type] = handler; + if (secondary != null) newCopy[secondary] = handler; + } + typeHandlers = newCopy; + } + + /// + /// Configure the specified type to be processed by a custom handler. + /// + /// The type to handle. + /// The handler for the type . + public static void AddTypeHandler(TypeHandler handler) => AddTypeHandlerImpl(typeof(T), handler, true); + + private static Dictionary typeHandlers; + + internal const string LinqBinary = "System.Data.Linq.Binary"; + + private const string ObsoleteInternalUsageOnly = "This method is for internal use only"; + + /// + /// Get the DbType that maps to a given value. + /// + /// The object to get a corresponding database type for. + [Obsolete(ObsoleteInternalUsageOnly, false)] +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + public static DbType GetDbType(object value) + { + if (value == null || value is DBNull) return DbType.Object; + + return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler handler); + } + + /// + /// OBSOLETE: For internal usage only. Lookup the DbType and handler for a given Type and member + /// + /// The type to lookup. + /// The name (for error messages). + /// Whether to demand a value (throw if missing). + /// The handler for . + [Obsolete(ObsoleteInternalUsageOnly, false)] +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) + { + handler = null; + 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 dbType)) + { + return dbType; + } + if (type.FullName == LinqBinary) + { + return DbType.Binary; + } + if (typeHandlers.TryGetValue(type, out handler)) + { + return DbType.Object; + } + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + return DynamicParameters.EnumerableMultiParameter; + } + +#if !NETSTANDARD1_3 && !NETSTANDARD2_0 + 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; + } +#endif + if (demand) + throw new NotSupportedException($"The member {name} of type {type.FullName} cannot be used as a parameter value"); + return DbType.Object; + } + + /// + /// Obtains the data as a list; if it is *already* a list, the original object is returned without + /// any duplication; otherwise, ToList() is invoked. + /// + /// The type of element in the list. + /// The enumerable to return as a list. + public static List AsList(this IEnumerable source) => + (source == null || source is List) ? (List)source : source.ToList(); + + /// + /// Execute parameterized SQL. + /// + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The number of rows affected. + public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); + return ExecuteImpl(cnn, ref command); + } + + /// + /// Execute parameterized SQL. + /// + /// The connection to execute on. + /// The command to execute on this connection. + /// The number of rows affected. + public static int Execute(this IDbConnection cnn, CommandDefinition command) => ExecuteImpl(cnn, ref command); + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell selected as . + public static object ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); + return ExecuteScalarImpl(cnn, ref command); + } + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The type to return. + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static T ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); + return ExecuteScalarImpl(cnn, ref command); + } + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImpl(cnn, ref command); + + /// + /// Execute parameterized SQL that selects a single value. + /// + /// The type to return. + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static T ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImpl(cnn, ref command); + + private static IEnumerable GetMultiExec(object param) + { + return (param is IEnumerable + && !(param is string + || param is IEnumerable> + || param is IDynamicParameters) + ) ? (IEnumerable)param : null; + } + + private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command) + { + object param = command.Parameters; + IEnumerable multiExec = GetMultiExec(param); + Identity identity; + CacheInfo info = null; + if (multiExec != null) + { + if ((command.Flags & CommandFlags.Pipelined) != 0) + { + // this includes all the code for concurrent/overlapped query + return ExecuteMultiImplAsync(cnn, command, multiExec).Result; + } + 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, command.AddToCache); + } + 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(); + } + } + command.OnCompleted(); + } + 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, command.AddToCache); + } + return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); + } + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + /// + /// + /// + /// + /// + public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); + return new WrappedReader(dbcmd, reader); + } + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The command to execute. + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) + { + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); + return new WrappedReader(dbcmd, reader); + } + + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The command to execute. + /// The flags for this reader. + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) + { + var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd); + return new WrappedReader(dbcmd, reader); + } + + /// + /// Return a sequence of dynamic objects with properties matching the columns. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer the results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => + Query(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); + + /// + /// Return a dynamic object with properties matching the columns. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryFirst(cnn, sql, param as object, transaction, commandTimeout, commandType); + + /// + /// Return a dynamic object with properties matching the columns. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryFirstOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + + /// + /// Return a dynamic object with properties matching the columns. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QuerySingle(cnn, sql, param as object, transaction, commandTimeout, commandType); + + /// + /// Return a dynamic object with properties matching the columns. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QuerySingleOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// 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). + /// + public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); + var data = QueryImpl(cnn, command, typeof(T)); + return command.Buffered ? data.ToList() : data; + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// 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). + /// + public static T QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.First, ref command, typeof(T)); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// 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). + /// + public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// 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). + /// + public static T QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// 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). + /// + public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// 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). + /// + public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); + var data = QueryImpl(cnn, command, type); + return command.Buffered ? data.ToList() : data; + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// 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). + /// + public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.First, ref command, type); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// 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). + /// + public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, type); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// 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). + /// + public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.Single, ref command, type); + } + + /// + /// Executes a single-row query, returning the data typed as . + /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// 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). + /// + public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, type); + } + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A sequence of data of ; 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). + /// + public static IEnumerable Query(this IDbConnection cnn, CommandDefinition command) + { + var data = QueryImpl(cnn, command, typeof(T)); + return command.Buffered ? data.ToList() : data; + } + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance or null 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). + /// + public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.First, ref command, typeof(T)); + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single or null instance 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). + /// + public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance 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). + /// + public static T QuerySingle(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance 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). + /// + public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); + + /// + /// Execute a command that returns multiple result sets, and access each in turn. + /// + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); + return QueryMultipleImpl(cnn, ref command); + } + + /// + /// Execute a command that returns multiple result sets, and access each in turn. + /// + /// The connection to query on. + /// The command to execute for this query. + public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command) => + QueryMultipleImpl(cnn, ref command); + + private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); + + IDbCommand cmd = null; + IDataReader reader = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + if (wasClosed) cnn.Open(); + cmd = command.SetupCommand(cnn, info.ParamReader); + reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess); + + var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache); + cmd = null; // now owned by result + 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(); + } + cmd?.Dispose(); + if (wasClosed) cnn.Close(); + throw; + } + } + + private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior) + { + try + { + return cmd.ExecuteReader(GetBehavior(wasClosed, behavior)); + } + catch (ArgumentException ex) + { // thanks, Sqlite! + if (Settings.DisableCommandBehaviorOptimizations(behavior, ex)) + { + // we can retry; this time it will have different flags + return cmd.ExecuteReader(GetBehavior(wasClosed, behavior)); + } + throw; + } + } + + private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var info = GetCacheInfo(identity, param, command.AddToCache); + + IDbCommand cmd = null; + IDataReader reader = null; + + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + cmd = command.SetupCommand(cnn, info.ParamReader); + + if (wasClosed) cnn.Open(); + reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + 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) + { + if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57 + yield break; + tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); + if (command.AddToCache) SetQueryCache(identity, info); + } + + var func = tuple.Func; + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + while (reader.Read()) + { + object val = func(reader); + if (val == null || val is T) + { + yield return (T)val; + } + else + { + yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + } + while (reader.NextResult()) { /* ignore subsequent result sets */ } + // happy path; close the reader cleanly - no + // need for "Cancel" etc + reader.Dispose(); + reader = null; + + command.OnCompleted(); + } + finally + { + if (reader != null) + { + if (!reader.IsClosed) + { + try { cmd.Cancel(); } + catch { /* don't spoil the existing exception */ } + } + reader.Dispose(); + } + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + } + + [Flags] + internal enum Row + { + First = 0, + FirstOrDefault = 1, // & FirstOrDefault != 0: allow zero rows + Single = 2, // & Single != 0: demand at least one row + SingleOrDefault = 3 + } + + private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = new int[0]; + private static void ThrowMultipleRows(Row row) + { + switch (row) + { // get the standard exception from the runtime + case Row.Single: ErrTwoRows.Single(); break; + case Row.SingleOrDefault: ErrTwoRows.SingleOrDefault(); break; + default: throw new InvalidOperationException(); + } + } + + private static void ThrowZeroRows(Row row) + { + switch (row) + { // get the standard exception from the runtime + case Row.First: ErrZeroRows.First(); break; + case Row.Single: ErrZeroRows.Single(); break; + default: throw new InvalidOperationException(); + } + } + + private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var info = GetCacheInfo(identity, param, command.AddToCache); + + IDbCommand cmd = null; + IDataReader reader = null; + + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + cmd = command.SetupCommand(cnn, info.ParamReader); + + if (wasClosed) cnn.Open(); + reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, (row & Row.Single) != 0 + ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition + : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow); + wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader + + T result = default(T); + if (reader.Read() && reader.FieldCount != 0) + { + // 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(effectiveType, reader, 0, -1, false)); + if (command.AddToCache) SetQueryCache(identity, info); + } + + var func = tuple.Func; + object val = func(reader); + if (val == null || val is T) + { + result = (T)val; + } + else + { + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); + while (reader.Read()) { /* ignore subsequent rows */ } + } + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one + { + ThrowZeroRows(row); + } + while (reader.NextResult()) { /* ignore subsequent result sets */ } + // happy path; close the reader cleanly - no + // need for "Cancel" etc + reader.Dispose(); + reader = null; + + command.OnCompleted(); + return result; + } + finally + { + if (reader != null) + { + if (!reader.IsClosed) + { + try { cmd.Cancel(); } + catch { /* don't spoil the existing exception */ } + } + reader.Dispose(); + } + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + } + + /// + /// Perform a multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 7 input types. If you need more types -> use Query with Type[] parameter. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with an arbitrary number of input types. + /// This returns a single type, combined from the raw types via . + /// + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// Array of types in the recordset. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); + var results = MultiMapImpl(cnn, command, types, map, splitOn, null, null, true); + return buffered ? results.ToList() : results; + } + + private static IEnumerable MultiMap( + this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) + { + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); + var results = MultiMapImpl(cnn, command, map, splitOn, null, null, true); + return buffered ? results.ToList() : results; + } + + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) + { + object param = command.Parameters; + identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); + CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); + + IDbCommand ownedCommand = null; + IDataReader ownedReader = null; + + bool wasClosed = cnn?.State == ConnectionState.Closed; + try + { + if (reader == null) + { + ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); + if (wasClosed) cnn.Open(); + ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + reader = ownedReader; + } + var deserializer = default(DeserializerState); + Func[] otherDeserializers; + + int hash = GetColumnHash(reader); + if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) + { + var deserializers = GenerateDeserializers(new[] { 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(); + if (command.AddToCache) SetQueryCache(identity, cinfo); + } + + Func mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); + + if (mapIt != null) + { + while (reader.Read()) + { + yield return mapIt(reader); + } + if (finalize) + { + while (reader.NextResult()) { /* ignore remaining result sets */ } + command.OnCompleted(); + } + } + } + finally + { + try + { + ownedReader?.Dispose(); + } + finally + { + ownedCommand?.Dispose(); + if (wasClosed) cnn.Close(); + } + } + } + + private static CommandBehavior GetBehavior(bool close, CommandBehavior @default) + { + return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors; + } + + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, IDataReader reader, Identity identity, bool finalize) + { + if (types.Length < 1) + { + throw new ArgumentException("you must provide at least one type to deserialize"); + } + + object param = command.Parameters; + identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); + + IDbCommand ownedCommand = null; + IDataReader ownedReader = null; + + bool wasClosed = cnn?.State == ConnectionState.Closed; + try + { + if (reader == null) + { + ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); + if (wasClosed) cnn.Open(); + ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + reader = ownedReader; + } + DeserializerState deserializer; + Func[] otherDeserializers; + + int hash = GetColumnHash(reader); + if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) + { + var deserializers = GenerateDeserializers(types, splitOn, reader); + deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); + otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); + SetQueryCache(identity, cinfo); + } + + Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); + + if (mapIt != null) + { + while (reader.Read()) + { + yield return mapIt(reader); + } + if (finalize) + { + while (reader.NextResult()) { /* ignore subsequent result sets */ } + command.OnCompleted(); + } + } + } + finally + { + try + { + ownedReader?.Dispose(); + } + finally + { + ownedCommand?.Dispose(); + if (wasClosed) cnn.Close(); + } + } + } + + private static Func GenerateMapper(Func deserializer, Func[] otherDeserializers, object map) + { + switch (otherDeserializers.Length) + { + case 1: + return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)); + case 2: + return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)); + case 3: + return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)); + case 4: + return r => ((Func)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)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)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)); + default: + throw new NotSupportedException(); + } + } + + private static Func GenerateMapper(int length, Func deserializer, Func[] otherDeserializers, Func map) + { + return r => + { + var objects = new object[length]; + objects[0] = deserializer(r); + + for (var i = 1; i < length; ++i) + { + objects[i] = otherDeserializers[i - 1](r); + } + + return map(objects); + }; + } + + private static Func[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader) + { + var deserializers = new List>(); + var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray(); + bool isMultiSplit = splits.Length > 1; + if (types[0] == 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 MultiMapException(reader); + } + + 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 MultiMapException(reader); + } + + private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache) + { + if (!TryGetQueryCache(identity, out CacheInfo info)) + { + if (GetMultiExec(exampleParameters) != null) + { + throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); + } + info = new CacheInfo(); + if (identity.parametersType != null) + { + Action reader; + if (exampleParameters is IDynamicParameters) + { + reader = (cmd, obj) => ((IDynamicParameters)obj).AddParameters(cmd, identity); + } + else if (exampleParameters is IEnumerable>) + { + reader = (cmd, obj) => + { + IDynamicParameters mapped = new DynamicParameters(obj); + mapped.AddParameters(cmd, identity); + }; + } + else + { + var literals = GetLiteralTokens(identity.sql); + reader = CreateParamInfoGenerator(identity, false, true, literals); + } + if ((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) + { + var tail = reader; + reader = (cmd, obj) => + { + tail(cmd, obj); + PassByPosition(cmd); + }; + } + info.ParamReader = reader; + } + if (addToCache) SetQueryCache(identity, info); + } + return info; + } + + private static bool ShouldPassByPosition(string sql) + { + return sql?.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql); + } + + private static void PassByPosition(IDbCommand cmd) + { + if (cmd.Parameters.Count == 0) return; + + Dictionary parameters = new Dictionary(StringComparer.Ordinal); + + foreach (IDbDataParameter param in cmd.Parameters) + { + if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param; + } + HashSet consumed = new HashSet(StringComparer.Ordinal); + bool firstMatch = true; + cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match => + { + string key = match.Groups[1].Value; + if (!consumed.Add(key)) + { + throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once"); + } + else if (parameters.TryGetValue(key, out IDbDataParameter param)) + { + if (firstMatch) + { + firstMatch = false; + cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully + } + // if found, return the anonymous token "?" + cmd.Parameters.Add(param); + parameters.Remove(key); + consumed.Add(key); + return "?"; + } + else + { + // otherwise, leave alone for simple debugging + return match.Value; + } + }); + } + + private static Func GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + { + // dynamic is passed in as Object ... by c# design + if (type == typeof(object) || type == typeof(DapperRow)) + { + return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); + } + Type underlyingType = null; + if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary + || (type.IsValueType() && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum()))) + { + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + { + return GetHandlerDeserializer(handler, type, startBound); + } + return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); + } + return GetStructDeserializer(type, underlyingType ?? type, startBound); + } + + private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) + { + return reader => handler.Parse(type, reader.GetValue(startBound)); + } + + private static Exception MultiMapException(IDataRecord reader) + { + bool hasFields = false; + try { hasFields = reader != null && reader.FieldCount != 0; } + catch { /* don't throw when trying to throw */ } + if (hasFields) + return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); + else + return new InvalidOperationException("No columns were selected"); + } + + internal static Func GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) + { + var fieldCount = reader.FieldCount; + if (length == -1) + { + length = fieldCount - startBound; + } + + if (fieldCount <= startBound) + { + throw MultiMapException(reader); + } + + 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) + { + for (int i = 0; i < values.Length; i++) + { + object val = r.GetValue(i); + values[i] = val is DBNull ? null : val; + } + } + 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); + }; + } + /// + /// Internal use only. + /// + /// The object to convert to a character. +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(ObsoleteInternalUsageOnly, false)] + public static char ReadChar(object value) + { + if (value == null || value is DBNull) throw new ArgumentNullException(nameof(value)); + var s = value as string; + if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); + return s[0]; + } + + /// + /// Internal use only. + /// + /// The object to convert to a character. +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(ObsoleteInternalUsageOnly, false)] + public static char? ReadNullableChar(object value) + { + if (value == null || value is DBNull) return null; + var s = value as string; + if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); + return s[0]; + } + + /// + /// Internal use only. + /// + /// The parameter collection to search in. + /// The command for this fetch. + /// The name of the parameter to get. +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(ObsoleteInternalUsageOnly, 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; + } + + internal static int GetListPaddingExtraCount(int count) + { + switch (count) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + return 0; // no padding + } + if (count < 0) return 0; + + int padFactor; + if (count <= 150) padFactor = 10; + else if (count <= 750) padFactor = 50; + else if (count <= 2000) padFactor = 100; // note: max param count for SQL Server + else if (count <= 2070) padFactor = 10; // try not to over-pad as we approach that limit + else if (count <= 2100) return 0; // just don't pad between 2070 and 2100, to minimize the crazy + else padFactor = 200; // above that, all bets are off! + + // if we have 17, factor = 10; 17 % 10 = 7, we need 3 more + int intoBlock = count % padFactor; + return intoBlock == 0 ? 0 : (padFactor - intoBlock); + } + + private static string GetInListRegex(string name, bool byPosition) => byPosition + ? (@"(\?)" + Regex.Escape(name) + @"\?(?!\w)(\s+(?i)unknown(?-i))?") + : ("([?@:]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); + + /// + /// Internal use only. + /// + /// The command to pack parameters for. + /// The name prefix for these parameters. + /// The parameter value can be an +#if !NETSTANDARD1_3 + [Browsable(false)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(ObsoleteInternalUsageOnly, 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 + + if (FeatureSupport.Get(command.Connection).Arrays) + { + var arrayParm = command.CreateParameter(); + arrayParm.Value = SanitizeParameterValue(value); + arrayParm.ParameterName = namePrefix; + command.Parameters.Add(arrayParm); + } + else + { + bool byPosition = ShouldPassByPosition(command.CommandText); + var list = value as IEnumerable; + var count = 0; + bool isString = value is IEnumerable; + bool isDbString = value is IEnumerable; + DbType dbType = 0; + + int splitAt = SqlMapper.Settings.InListStringSplitCount; + bool viaSplit = splitAt >= 0 + && TryStringSplit(ref list, splitAt, namePrefix, command, byPosition); + + if (list != null && !viaSplit) + { + object lastValue = null; + foreach (var item in list) + { + if (++count == 1) // first item: fetch some type info + { + if (item == null) + { + throw new NotSupportedException("The first item in a list-expansion cannot be null"); + } + if (!isDbString) + { + dbType = LookupDbType(item.GetType(), "", true, out ITypeHandler handler); + } + } + var nextName = namePrefix + count.ToString(); + if (isDbString && item is DbString) + { + var str = item as DbString; + str.AddParameter(command, nextName); + } + else + { + var listParam = command.CreateParameter(); + listParam.ParameterName = nextName; + if (isString) + { + listParam.Size = DbString.DefaultLength; + if (item != null && ((string)item).Length > DbString.DefaultLength) + { + listParam.Size = -1; + } + } + + var tmp = listParam.Value = SanitizeParameterValue(item); + if (tmp != null && !(tmp is DBNull)) + lastValue = tmp; // only interested in non-trivial values for padding + + if (listParam.DbType != dbType) + { + listParam.DbType = dbType; + } + command.Parameters.Add(listParam); + } + } + if (Settings.PadListExpansions && !isDbString && lastValue != null) + { + int padCount = GetListPaddingExtraCount(count); + for (int i = 0; i < padCount; i++) + { + count++; + var padParam = command.CreateParameter(); + padParam.ParameterName = namePrefix + count.ToString(); + if (isString) padParam.Size = DbString.DefaultLength; + padParam.DbType = dbType; + padParam.Value = lastValue; + command.Parameters.Add(padParam); + } + } + } + + if (viaSplit) + { + // already done + } + else + { + var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); + 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)"; + } + }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); + 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 = GetStringBuilder().Append(variableName).Append(1).Append(suffix); + for (int i = 2; i <= count; i++) + { + sb.Append(',').Append(variableName).Append(i).Append(suffix); + } + return sb.__ToStringRecycle(); + } + else + { + var sb = GetStringBuilder().Append('(').Append(variableName); + if (!byPosition) sb.Append(1); + for (int i = 2; i <= count; i++) + { + sb.Append(',').Append(variableName); + if (!byPosition) sb.Append(i); + } + return sb.Append(')').__ToStringRecycle(); + } + }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); + } + } + } + } + + private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) + { + if (list == null || splitAt < 0) return false; + switch (list) + { + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "bigint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "smallint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "tinyint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + } + return false; + } + + private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, + Action append) + { + var typed = list as ICollection; + if (typed == null) + { + typed = list.ToList(); + list = typed; // because we still need to be able to iterate it, even if we fail here + } + if (typed.Count < splitAt) return false; + + string varName = null; + var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); + var sql = 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 + { + varName = variableName; + return "(select cast([value] as " + colType + ") from string_split(" + variableName + ",','))"; + } + }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); + if (varName == null) return false; // couldn't resolve the var! + + command.CommandText = sql; + var concatenatedParam = command.CreateParameter(); + concatenatedParam.ParameterName = namePrefix; + concatenatedParam.DbType = DbType.AnsiString; + concatenatedParam.Size = -1; + string val; + using (var iter = typed.GetEnumerator()) + { + if (iter.MoveNext()) + { + var sb = GetStringBuilder(); + append(sb, iter.Current); + while (iter.MoveNext()) + { + append(sb.Append(','), iter.Current); + } + val = sb.ToString(); + } + else + { + val = ""; + } + } + concatenatedParam.Value = val; + command.Parameters.Add(concatenatedParam); + return true; + } + + /// + /// OBSOLETE: For internal usage only. Sanitizes the paramter value with proper type casting. + /// + /// The value to sanitize. + [Obsolete(ObsoleteInternalUsageOnly, false)] + public static object SanitizeParameterValue(object value) + { + if (value == null) return DBNull.Value; + if (value is Enum) + { + TypeCode typeCode; + if (value is IConvertible) + { + typeCode = ((IConvertible)value).GetTypeCode(); + } + else + { + typeCode = TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); + } + switch (typeCode) + { + case TypeCode.Byte: return (byte)value; + case TypeCode.SByte: return (sbyte)value; + case TypeCode.Int16: return (short)value; + case TypeCode.Int32: return (int)value; + case TypeCode.Int64: return (long)value; + case TypeCode.UInt16: return (ushort)value; + case TypeCode.UInt32: return (uint)value; + case TypeCode.UInt64: return (ulong)value; + } + } + return value; + } + + private static IEnumerable FilterParameters(IEnumerable parameters, string sql) + { + var list = new List(16); + foreach (var p in parameters) + { + if (Regex.IsMatch(sql, @"[?@:]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) + list.Add(p); + } + return list; + } + + // look for ? / @ / : *by itself* + private static readonly Regex smellsLikeOleDb = new Regex(@"(? + /// Replace all literal tokens with their text form. + /// + /// The parameter lookup to do replacements with. + /// The command to repalce parameters in. + 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); + + /// + /// Convert numeric values to their string form for SQL literal purposes. + /// + /// The value to get a string for. + [Obsolete(ObsoleteInternalUsageOnly)] + public static string Format(object value) + { + if (value == null) + { + return "null"; + } + else + { + switch (TypeExtensions.GetTypeCode(value.GetType())) + { +#if !NETSTANDARD1_3 + case TypeCode.DBNull: + return "null"; +#endif + 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: + var multiExec = GetMultiExec(value); + if (multiExec != null) + { + StringBuilder sb = null; + bool first = true; + foreach (object subval in multiExec) + { + if (first) + { + sb = GetStringBuilder().Append('('); + first = false; + } + else + { + sb.Append(','); + } + sb.Append(Format(subval)); + } + if (first) + { + return "(select null where 1=0)"; + } + else + { + return sb.Append(')').__ToStringRecycle(); + } + } + throw new NotSupportedException($"The type '{value.GetType().Name}' is not supported for SQL literals."); + } + } + } + + internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList 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 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(StringComparer.Ordinal); + List list = new List(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; + } + + /// + /// Internal use only. + /// + /// The identity of the generator. + /// Whether to check for duplicates. + /// Whether to remove unused parameters. + public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => + CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); + + private static bool IsValueTuple(Type type) => type?.IsValueType() == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); + + private static List GetValueTupleMembers(Type type, string[] names) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + var result = new List(names.Length); + for (int i = 0; i < names.Length; i++) + { + FieldInfo field = null; + string name = "Item" + (i + 1).ToString(CultureInfo.InvariantCulture); + foreach (var test in fields) + { + if (test.Name == name) + { + field = test; + break; + } + } + result.Add(field == null ? null : new SimpleMemberMap(string.IsNullOrWhiteSpace(names[i]) ? name : names[i], field)); + } + return result; + } + + internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) + { + Type type = identity.parametersType; + + if (IsValueTuple(type)) + { + throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing"); + } + + bool filterParams = false; + if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) + { + filterParams = !smellsLikeOleDb.IsMatch(identity.sql); + } + var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); + + var il = dm.GetILGenerator(); + + bool isStruct = type.IsValueType(); + bool haveInt32Arg1 = false; + il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] + if (isStruct) + { + il.DeclareLocal(type.MakeByRefType()); // note: ref-local + il.Emit(OpCodes.Unbox, type); // stack is now [typed-param] + } + else + { + il.DeclareLocal(type); // 0 + il.Emit(OpCodes.Castclass, 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(nameof(IDbCommand.Parameters)).GetGetMethod(), null); // stack is now [parameters] + + var allTypeProps = type.GetProperties(); + var propsList = new List(allTypeProps.Length); + for (int i = 0; i < allTypeProps.Length; ++i) + { + var p = allTypeProps[i]; + if (p.GetIndexParameters().Length == 0) + propsList.Add(p); + } + + var ctors = type.GetConstructors(); + ParameterInfo[] ctorParams; + IEnumerable props = null; + // try to detect tuple patterns, e.g. anon-types, and use that to choose the order + // otherwise: alphabetical + if (ctors.Length == 1 && propsList.Count == (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 < propsList.Count; i++) + { + if (!string.Equals(propsList[i].Name, ctorParams[i].Name, StringComparison.OrdinalIgnoreCase)) + { + ok = false; + break; + } + } + if (ok) + { + // pre-sorted; the reflection gods have smiled upon us + props = propsList; + } + else + { // might still all be accounted for; check the hard way + var positionByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var param in ctorParams) + { + positionByName[param.Name] = param.Position; + } + if (positionByName.Count == propsList.Count) + { + int[] positions = new int[propsList.Count]; + ok = true; + for (int i = 0; i < propsList.Count; i++) + { + if (!positionByName.TryGetValue(propsList[i].Name, out int pos)) + { + ok = false; + break; + } + positions[i] = pos; + } + if (ok) + { + props = propsList.ToArray(); + Array.Sort(positions, (PropertyInfo[])props); + } + } + } + } + if (props == null) + { + propsList.Sort(new PropertyInfoByNameComparer()); + props = propsList; + } + if (filterParams) + { + props = FilterParameters(props, identity.sql); + } + + var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt; + foreach (var prop in props) + { + if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType)) + { + il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param] + il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [custom] + il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] + il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] + il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter)), null); // stack is now [parameters] + continue; + } +#pragma warning disable 618 + DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); +#pragma warning restore 618 + 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(callOpCode, 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(nameof(SqlMapper.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(nameof(SqlMapper.FindOrAddParameter)), null); // stack is [parameters] [parameter] + } + else + { + // no risk of duplicates; just blindly add + il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod(nameof(IDbCommand.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(nameof(IDataParameter.ParameterName)).GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] + } + if (dbType != DbType.Time && handler == null) // 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] + if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic + { + // look it up from the param value + il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] + il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] + il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.GetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + } + else + { + // constant value; nice and simple + EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + } + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.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(nameof(IDataParameter.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(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] + bool checkForNull; + if (prop.PropertyType.IsValueType()) + { + var propType = prop.PropertyType; + var nullType = Nullable.GetUnderlyingType(propType); + bool callSanitize = false; + + if ((nullType ?? propType).IsEnum()) + { + if (nullType != null) + { + // Nullable; we want to box as the underlying type; that's just *hard*; for + // simplicity, box as Nullable and call SanitizeParameterValue + callSanitize = checkForNull = true; + } + else + { + checkForNull = false; + // non-nullable enum; we can do that! just box to the wrong type! (no, really) + switch (TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(propType))) + { + case TypeCode.Byte: propType = typeof(byte); break; + case TypeCode.SByte: propType = typeof(sbyte); break; + case TypeCode.Int16: propType = typeof(short); break; + case TypeCode.Int32: propType = typeof(int); break; + case TypeCode.Int64: propType = typeof(long); break; + case TypeCode.UInt16: propType = typeof(ushort); break; + case TypeCode.UInt32: propType = typeof(uint); break; + case TypeCode.UInt64: propType = typeof(ulong); break; + } + } + } + else + { + checkForNull = nullType != null; + } + il.Emit(OpCodes.Box, propType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] + if (callSanitize) + { + checkForNull = false; // handled by sanitize + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SanitizeParameterValue)), null); + // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] + } + } + else + { + checkForNull = true; // if not a value-type, need to check + } + if (checkForNull) + { + if ((dbType == DbType.String || dbType == DbType.AnsiString) && !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 || dbType == DbType.AnsiString) ? 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(nameof(DBNull.Value))); // relative stack [DBNull] + if (dbType == DbType.String || dbType == DbType.AnsiString) + { + 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(nameof(string.Length)).GetGetMethod(), null); // [string] [length] + EmitInt32(il, DbString.DefaultLength); // [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, DbString.DefaultLength); // [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] + } + + if (handler != null) + { +#pragma warning disable 618 + il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod(nameof(TypeHandlerCache.SetValue))); // stack is now [parameters] [[parameters]] [parameter] +#pragma warning restore 618 + } + else + { + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.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(nameof(IDbDataParameter.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(nameof(IList.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 && propsList != null) + { + il.Emit(OpCodes.Ldarg_0); // command + il.Emit(OpCodes.Ldarg_0); // command, command + var cmdText = typeof(IDbCommand).GetProperty(nameof(IDbCommand.CommandText)); + il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql + Dictionary 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 < propsList.Count; i++) + { + string thisName = propsList[i].Name; + if (string.Equals(thisName, huntName, StringComparison.OrdinalIgnoreCase)) + { + fallback = propsList[i]; + if (string.Equals(thisName, huntName, StringComparison.Ordinal)) + { + 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(callOpCode, prop.GetGetMethod(), null); // command, sql, typed value + Type propType = prop.PropertyType; + var typeCode = TypeExtensions.GetTypeCode(propType); + switch (typeCode) + { + case TypeCode.Boolean: + Label ifTrue = il.DefineLabel(), allDone = il.DefineLabel(); + il.Emit(OpCodes.Brtrue_S, ifTrue); + il.Emit(OpCodes.Ldstr, "0"); + il.Emit(OpCodes.Br_S, allDone); + il.MarkLabel(ifTrue); + il.Emit(OpCodes.Ldstr, "1"); + il.MarkLabel(allDone); + break; + 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: + // need 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(); + 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)dm.CreateDelegate(typeof(Action)); + } + + private static readonly Dictionary 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 => TypeExtensions.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })); + + private static MethodInfo GetToString(TypeCode typeCode) + { + return toStrings.TryGetValue(typeCode, out MethodInfo method) ? method : null; + } + + private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) }), + InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static).GetGetMethod(); + + private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action paramReader) + { + IDbCommand cmd = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + try + { + cmd = command.SetupCommand(cnn, paramReader); + if (wasClosed) cnn.Open(); + int result = cmd.ExecuteNonQuery(); + command.OnCompleted(); + return result; + } + finally + { + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + } + + private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition command) + { + Action paramReader = null; + object param = command.Parameters; + if (param != null) + { + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; + } + + IDbCommand cmd = null; + bool wasClosed = cnn.State == ConnectionState.Closed; + object result; + try + { + cmd = command.SetupCommand(cnn, paramReader); + if (wasClosed) cnn.Open(); + result = cmd.ExecuteScalar(); + command.OnCompleted(); + } + finally + { + if (wasClosed) cnn.Close(); + cmd?.Dispose(); + } + return Parse(result); + } + + private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd) + { + Action paramReader = GetParameterReader(cnn, ref command); + cmd = null; + bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; + try + { + cmd = command.SetupCommand(cnn, paramReader); + if (wasClosed) cnn.Open(); + var reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, commandBehavior); + wasClosed = false; // don't dispose before giving it to them! + disposeCommand = false; + // note: command.FireOutputCallbacks(); would be useless here; parameters come at the **end** of the TDS stream + return reader; + } + finally + { + if (wasClosed) cnn.Close(); + if (cmd != null && disposeCommand) cmd.Dispose(); + } + } + + private static Action GetParameterReader(IDbConnection cnn, ref CommandDefinition command) + { + object param = command.Parameters; + IEnumerable multiExec = GetMultiExec(param); + CacheInfo info = null; + if (multiExec != null) + { + throw new NotSupportedException("MultiExec is not supported by ExecuteReader"); + } + + // nice and simple + if (param != null) + { + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + info = GetCacheInfo(identity, param, command.AddToCache); + } + var paramReader = info?.ParamReader; + return paramReader; + } + + private static Func 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 => ReadChar(r.GetValue(index)); + } + if (type == typeof(char?)) + { + return r => 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); + if (val is float || val is double || val is decimal) + { + val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture); + } + return val is DBNull ? null : Enum.ToObject(effectiveType, val); + }; + } + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + { + return r => + { + var val = r.GetValue(index); + return val is DBNull ? null : handler.Parse(type, val); + }; + } + return r => + { + var val = r.GetValue(index); + return val is DBNull ? null : val; + }; + } + + private static T Parse(object value) + { + if (value == null || value is DBNull) return default(T); + if (value is T) return (T)value; + var type = typeof(T); + type = Nullable.GetUnderlyingType(type) ?? type; + if (type.IsEnum()) + { + if (value is float || value is double || value is decimal) + { + value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture); + } + return (T)Enum.ToObject(type, value); + } + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + { + return (T)handler.Parse(type, value); + } + return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + + private static readonly MethodInfo + enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) }), + getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) + .Select(p => p.GetGetMethod()).First(); + + /// + /// Gets type-map for the given type + /// + /// Type map instance, default is to create new instance of DefaultTypeMap + public static Func TypeMapProvider = (Type type) => new DefaultTypeMap(type); + + /// + /// Gets type-map for the given . + /// + /// The type to get a map for. + /// Type map implementation, DefaultTypeMap instance if no override present + public static ITypeMap GetTypeMap(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(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 = TypeMapProvider(type); + _typeMaps[type] = map; + } + } + } + return map; + } + + // use Hashtable to get free lockless reading + private static readonly Hashtable _typeMaps = new Hashtable(); + + /// + /// Set custom mapping for type deserializers + /// + /// Entity type to override + /// Mapping rules impementation, null to remove custom map + public static void SetTypeMap(Type type, ITypeMap map) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (map == null || map is DefaultTypeMap) + { + lock (_typeMaps) + { + _typeMaps.Remove(type); + } + } + else + { + lock (_typeMaps) + { + _typeMaps[type] = map; + } + } + + PurgeQueryCacheByType(type); + } + + /// + /// Internal use only + /// + /// + /// + /// + /// + /// + /// + public static Func GetTypeDeserializer( + Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false + ) + { + return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing); + } + + private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary locals, Type type, bool initAndLoad) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + locals = locals ?? new Dictionary(); + if (!locals.TryGetValue(type, out LocalBuilder found)) + { + found = il.DeclareLocal(type); + locals.Add(type, found); + } + if (initAndLoad) + { + il.Emit(OpCodes.Ldloca, (short)found.LocalIndex); + il.Emit(OpCodes.Initobj, type); + il.Emit(OpCodes.Ldloca, (short)found.LocalIndex); + il.Emit(OpCodes.Ldobj, type); + } + return found; + } + + private static Func GetTypeDeserializerImpl( + Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false + ) + { + var returnType = type.IsValueType() ? typeof(object) : type; + var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, 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 MultiMapException(reader); + } + + var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); + + ITypeMap typeMap = GetTypeMap(type); + + int index = startBound; + ConstructorInfo specializedConstructor = null; + +#if !NETSTANDARD1_3 + bool supportInitialize = false; +#endif + Dictionary structLocals = null; + 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 explicitConstr = typeMap.FindExplicitConstructor(); + if (explicitConstr != null) + { + var consPs = explicitConstr.GetParameters(); + foreach (var p in consPs) + { + if (!p.ParameterType.IsValueType()) + { + il.Emit(OpCodes.Ldnull); + } + else + { + GetTempLocal(il, ref structLocals, p.ParameterType, true); + } + } + + il.Emit(OpCodes.Newobj, explicitConstr); + il.Emit(OpCodes.Stloc_1); +#if !NETSTANDARD1_3 + supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); + if (supportInitialize) + { + il.Emit(OpCodes.Ldloc_1); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); + } +#endif + } + else + { + 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($"A parameterless default constructor or one matching signature {proposedTypes} is required for {type.FullName} materialization"); + } + + if (ctor.GetParameters().Length == 0) + { + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Stloc_1); +#if !NETSTANDARD1_3 + supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); + if (supportInitialize) + { + il.Emit(OpCodes.Ldloc_1); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); + } +#endif + } + 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 = IsValueTuple(type) ? GetValueTupleMembers(type, names) : ((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, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex; + bool applyNullSetting = Settings.ApplyNullValues; + 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] + 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; + + if (memberType == typeof(char) || memberType == typeof(char?)) + { + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( + memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.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?.IsEnum() == true ? nullUnderlyingType : memberType; + + if (unboxType.IsEnum()) + { + Type numericType = Enum.GetUnderlyingType(unboxType); + if (colType == typeof(string)) + { + if (enumDeclareLocal == -1) + { + enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; + } + il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string] + 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(nameof(Type.GetTypeFromHandle)), null);// stack is now [target][target][enum-type] + 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] + } + else + { + FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + } + + if (nullUnderlyingType != null) + { + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-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 + { + TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); + bool hasTypeHandler; + if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) + { + if (hasTypeHandler) + { +#pragma warning disable 618 + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [target][target][typed-value] +#pragma warning restore 618 + } + else + { + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] + } + } + else + { + // not a direct match; need to tweak the unbox + FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); + 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) + { + il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + } + 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 if (applyNullSetting && (!memberType.IsValueType() || Nullable.GetUnderlyingType(memberType) != null)) + { + il.Emit(OpCodes.Pop); // stack is now [target][target] + // can load a null with this value + if (memberType.IsValueType()) + { // must be Nullable for some T + GetTempLocal(il, ref structLocals, memberType, true); // stack is now [target][target][null] + } + else + { // regular reference-type + il.Emit(OpCodes.Ldnull); // stack is now [target][target][null] + } + + // Store the value in the property/field + if (item.Property != null) + { + il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + // stack is now [target] + } + else + { + il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] + } + } + 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++; + } + 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 !NETSTANDARD1_3 + if (supportInitialize) + { + il.Emit(OpCodes.Ldloc_1); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit)), null); + } +#endif + } + 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 + LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException)), null); + il.EndExceptionBlock(); + + il.Emit(OpCodes.Ldloc_1); // stack is [rval] + if (type.IsValueType()) + { + il.Emit(OpCodes.Box, type); + } + il.Emit(OpCodes.Ret); + + var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType); + return (Func)dm.CreateDelegate(funcType); + } + + private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via) + { + MethodInfo op; + if (from == (via ?? to)) + { + il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] + } + else if ((op = GetOperator(from, to)) != null) + { + // this is handy for things like decimal <===> double + il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value] + il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value] + } + else + { + bool handled = false; + OpCode opCode = default(OpCode); + switch (TypeExtensions.GetTypeCode(from)) + { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + handled = true; + switch (TypeExtensions.GetTypeCode(via ?? to)) + { + 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; + } + break; + } + if (handled) + { + il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][col-typed-value] + il.Emit(opCode); // stack is now [target][target][typed-value] + if (to == typeof(bool)) + { // 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 + { + il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null); // stack is now [target][target][value][member-type] + il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value] + il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] + } + } + } + + private 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"); + } + + private 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(nameof(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(nameof(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(nameof(index)); + + if (index <= 255) + { + il.Emit(OpCodes.Ldloca_S, (byte)index); + } + else + { + il.Emit(OpCodes.Ldloca, (short)index); + } + } + + /// + /// Throws a data exception, only used internally + /// + /// The exception to throw. + /// The index the exception occured at. + /// The reader the exception occured in. + /// The value that caused the exception. + [Obsolete(ObsoleteInternalUsageOnly, false)] + public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value) + { + Exception toThrow; + try + { + string name = "(n/a)", formattedValue = "(n/a)"; + if (reader != null && index >= 0 && index < reader.FieldCount) + { + name = reader.GetName(index); + try + { + if (value == null || value is DBNull) + { + formattedValue = ""; + } + else + { + formattedValue = Convert.ToString(value) + " - " + TypeExtensions.GetTypeCode(value.GetType()); + } + } + catch (Exception valEx) + { + formattedValue = valEx.Message; + } + } + toThrow = new DataException($"Error parsing column {index} ({name}={formattedValue})", 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; + } + } + + /// + /// 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 strategies. Note that usual equivalence rules apply: any equivalent connection strings + /// MUST yield the same hash-code. + /// + public static IEqualityComparer ConnectionStringComparer + { + get { return connectionStringComparer; } + set { connectionStringComparer = value ?? StringComparer.Ordinal; } + } + + private static IEqualityComparer connectionStringComparer = StringComparer.Ordinal; + +#if !NETSTANDARD1_3 + /// + /// Key used to indicate the type name associated with a DataTable. + /// + private const string DataTableTypeNameKey = "dapper:TypeName"; + + /// + /// Used to pass a DataTable as a . + /// + /// The to create this parameter for. + /// The name of the type this parameter is for. + public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null) => + new TableValuedParameter(table, typeName); + + /// + /// Associate a DataTable with a type name. + /// + /// The that does with the . + /// The name of the type this table is for. + public static void SetTypeName(this DataTable table, string typeName) + { + if (table != null) + { + if (string.IsNullOrEmpty(typeName)) + table.ExtendedProperties.Remove(DataTableTypeNameKey); + else + table.ExtendedProperties[DataTableTypeNameKey] = typeName; + } + } + + /// + /// Fetch the type name associated with a . + /// + /// The that has a type name associated with it. + public static string GetTypeName(this DataTable table) => + table?.ExtendedProperties[DataTableTypeNameKey] as string; +#endif + + /// + /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. + /// + /// The list of records to convert to TVPs. + /// The sql parameter type name. + public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) => + new SqlDataRecordListTVPParameter(list, typeName); + + // one per thread + [ThreadStatic] + private static StringBuilder perThreadStringBuilderCache; + private static StringBuilder GetStringBuilder() + { + var tmp = perThreadStringBuilderCache; + if (tmp != null) + { + perThreadStringBuilderCache = null; + tmp.Length = 0; + return tmp; + } + return new StringBuilder(); + } + + private static string __ToStringRecycle(this StringBuilder obj) + { + if (obj == null) return ""; + var s = obj.ToString(); + perThreadStringBuilderCache = perThreadStringBuilderCache ?? obj; + return s; + } + } +} diff --git a/Dapper/TableValuedParameter.cs b/Dapper/TableValuedParameter.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f8603f940eaf977cf39408cfbb58b6bb307a7cf --- /dev/null +++ b/Dapper/TableValuedParameter.cs @@ -0,0 +1,69 @@ +using System; +using System.Data; +using System.Reflection; + +#if !NETSTANDARD1_3 +namespace Dapper +{ + /// + /// Used to pass a DataTable as a TableValuedParameter + /// + internal sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter + { + private readonly DataTable table; + private readonly string typeName; + + /// + /// Create a new instance of . + /// + /// The to create this parameter for + public TableValuedParameter(DataTable table) : this(table, null) { /* run base */ } + + /// + /// Create a new instance of . + /// + /// The to create this parameter for. + /// The name of the type this parameter is for. + public TableValuedParameter(DataTable table, string typeName) + { + this.table = table; + this.typeName = typeName; + } + + private static readonly Action setTypeName; + static TableValuedParameter() + { + var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty("TypeName", BindingFlags.Instance | BindingFlags.Public); + if (prop != null && prop.PropertyType == typeof(string) && prop.CanWrite) + { + setTypeName = (Action) + Delegate.CreateDelegate(typeof(Action), prop.GetSetMethod()); + } + } + + void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) + { + var param = command.CreateParameter(); + param.ParameterName = name; + Set(param, table, typeName); + command.Parameters.Add(param); + } + + internal static void Set(IDbDataParameter parameter, DataTable table, string typeName) + { +#pragma warning disable 0618 + parameter.Value = SqlMapper.SanitizeParameterValue(table); +#pragma warning restore 0618 + if (string.IsNullOrEmpty(typeName) && table != null) + { + typeName = table.GetTypeName(); + } + if (!string.IsNullOrEmpty(typeName) && (parameter is System.Data.SqlClient.SqlParameter sqlParam)) + { + setTypeName?.Invoke(sqlParam, typeName); + sqlParam.SqlDbType = SqlDbType.Structured; + } + } + } +} +#endif \ No newline at end of file diff --git a/Dapper/TypeExtensions.cs b/Dapper/TypeExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..546d492f98251074ba4fc648fcd59a93cba2cfbe --- /dev/null +++ b/Dapper/TypeExtensions.cs @@ -0,0 +1,96 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +namespace Dapper +{ + internal static class TypeExtensions + { + public static string Name(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().Name; +#else + type.Name; +#endif + + public static bool IsValueType(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsValueType; +#else + type.IsValueType; +#endif + + public static bool IsEnum(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsEnum; +#else + type.IsEnum; +#endif + + public static bool IsGenericType(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsGenericType; +#else + type.IsGenericType; +#endif + + public static bool IsInterface(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsInterface; +#else + type.IsInterface; +#endif + +#if NETSTANDARD1_3 || NETCOREAPP1_0 + public static IEnumerable GetCustomAttributes(this Type type, bool inherit) + { + return type.GetTypeInfo().GetCustomAttributes(inherit); + } + + public static TypeCode GetTypeCode(Type type) + { + if (type == null) return TypeCode.Empty; + if (typeCodeLookup.TryGetValue(type, out TypeCode result)) return result; + + if (type.IsEnum()) + { + type = Enum.GetUnderlyingType(type); + if (typeCodeLookup.TryGetValue(type, out result)) return result; + } + return TypeCode.Object; + } + + private static readonly Dictionary typeCodeLookup = new Dictionary + { + [typeof(bool)] = TypeCode.Boolean, + [typeof(byte)] = TypeCode.Byte, + [typeof(char)] = TypeCode.Char, + [typeof(DateTime)] = TypeCode.DateTime, + [typeof(decimal)] = TypeCode.Decimal, + [typeof(double)] = TypeCode.Double, + [typeof(short)] = TypeCode.Int16, + [typeof(int)] = TypeCode.Int32, + [typeof(long)] = TypeCode.Int64, + [typeof(object)] = TypeCode.Object, + [typeof(sbyte)] = TypeCode.SByte, + [typeof(float)] = TypeCode.Single, + [typeof(string)] = TypeCode.String, + [typeof(ushort)] = TypeCode.UInt16, + [typeof(uint)] = TypeCode.UInt32, + [typeof(ulong)] = TypeCode.UInt64, + }; +#else + public static TypeCode GetTypeCode(Type type) => Type.GetTypeCode(type); +#endif + + public static MethodInfo GetPublicInstanceMethod(this Type type, string name, Type[] types) + { +#if NETSTANDARD1_3 || NETCOREAPP1_0 + var method = type.GetMethod(name, types); + return (method?.IsPublic == true && !method.IsStatic) ? method : null; +#else + return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); +#endif + } + } +} diff --git a/Dapper/UdtTypeHandler.cs b/Dapper/UdtTypeHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..ffbf01d5d082452c38de424a73bf0bbd92e86fbd --- /dev/null +++ b/Dapper/UdtTypeHandler.cs @@ -0,0 +1,45 @@ +using System; +using System.Data; + +namespace Dapper +{ + public static partial class SqlMapper + { +#if !NETSTANDARD1_3 && !NETSTANDARD2_0 + /// + /// A type handler for data-types that are supported by the underlying provider, but which need + /// a well-known UdtTypeName to be specified + /// + public class UdtTypeHandler : ITypeHandler + { + private readonly string udtTypeName; + /// + /// Creates a new instance of UdtTypeHandler with the specified . + /// + /// The user defined type name. + 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) + { +#pragma warning disable 0618 + parameter.Value = SanitizeParameterValue(value); +#pragma warning restore 0618 + if (parameter is System.Data.SqlClient.SqlParameter && !(value is DBNull)) + { + ((System.Data.SqlClient.SqlParameter)parameter).SqlDbType = SqlDbType.Udt; + ((System.Data.SqlClient.SqlParameter)parameter).UdtTypeName = udtTypeName; + } + } + } +#endif + } +} diff --git a/Dapper/WrappedDataReader.cs b/Dapper/WrappedDataReader.cs new file mode 100644 index 0000000000000000000000000000000000000000..55d2e6df17d43d981fe41614cdb68b35b6324519 --- /dev/null +++ b/Dapper/WrappedDataReader.cs @@ -0,0 +1,20 @@ +using System.Data; + +namespace Dapper +{ + /// + /// Describes a reader that controls the lifetime of both a command and a reader, + /// exposing the downstream command/reader as properties. + /// + public interface IWrappedDataReader : IDataReader + { + /// + /// Obtain the underlying reader + /// + IDataReader Reader { get; } + /// + /// Obtain the underlying command + /// + IDbCommand Command { get; } + } +} diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs new file mode 100644 index 0000000000000000000000000000000000000000..9165150627149e21c98d148402554ac00471e247 --- /dev/null +++ b/Dapper/WrappedReader.cs @@ -0,0 +1,112 @@ +using System; +using System.Data; + +namespace Dapper +{ + internal class WrappedReader : IWrappedDataReader + { + private IDataReader reader; + private IDbCommand cmd; + + public IDataReader Reader + { + get + { + var tmp = reader; + if (tmp == null) throw new ObjectDisposedException(GetType().Name); + return tmp; + } + } + + IDbCommand IWrappedDataReader.Command + { + get + { + var tmp = cmd; + if (tmp == null) throw new ObjectDisposedException(GetType().Name); + return tmp; + } + } + + public WrappedReader(IDbCommand cmd, IDataReader reader) + { + this.cmd = cmd; + this.reader = reader; + } + + void IDataReader.Close() => reader?.Close(); + + int IDataReader.Depth => Reader.Depth; + + DataTable IDataReader.GetSchemaTable() => Reader.GetSchemaTable(); + + bool IDataReader.IsClosed => reader?.IsClosed ?? true; + + bool IDataReader.NextResult() => Reader.NextResult(); + + bool IDataReader.Read() => Reader.Read(); + + int IDataReader.RecordsAffected => Reader.RecordsAffected; + + void IDisposable.Dispose() + { + reader?.Close(); + reader?.Dispose(); + reader = null; + cmd?.Dispose(); + cmd = null; + } + + int IDataRecord.FieldCount => Reader.FieldCount; + + bool IDataRecord.GetBoolean(int i) => Reader.GetBoolean(i); + + byte IDataRecord.GetByte(int i) => Reader.GetByte(i); + + long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => + Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + + char IDataRecord.GetChar(int i) => Reader.GetChar(i); + + long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => + Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); + + IDataReader IDataRecord.GetData(int i) => Reader.GetData(i); + + string IDataRecord.GetDataTypeName(int i) => Reader.GetDataTypeName(i); + + DateTime IDataRecord.GetDateTime(int i) => Reader.GetDateTime(i); + + decimal IDataRecord.GetDecimal(int i) => Reader.GetDecimal(i); + + double IDataRecord.GetDouble(int i) => Reader.GetDouble(i); + + Type IDataRecord.GetFieldType(int i) => Reader.GetFieldType(i); + + float IDataRecord.GetFloat(int i) => Reader.GetFloat(i); + + Guid IDataRecord.GetGuid(int i) => Reader.GetGuid(i); + + short IDataRecord.GetInt16(int i) => Reader.GetInt16(i); + + int IDataRecord.GetInt32(int i) => Reader.GetInt32(i); + + long IDataRecord.GetInt64(int i) => Reader.GetInt64(i); + + string IDataRecord.GetName(int i) => Reader.GetName(i); + + int IDataRecord.GetOrdinal(string name) => Reader.GetOrdinal(name); + + string IDataRecord.GetString(int i) => Reader.GetString(i); + + object IDataRecord.GetValue(int i) => Reader.GetValue(i); + + int IDataRecord.GetValues(object[] values) => Reader.GetValues(values); + + bool IDataRecord.IsDBNull(int i) => Reader.IsDBNull(i); + + object IDataRecord.this[string name] => Reader[name]; + + object IDataRecord.this[int i] => Reader[i]; + } +} diff --git a/Dapper/XmlHandlers.cs b/Dapper/XmlHandlers.cs new file mode 100644 index 0000000000000000000000000000000000000000..d230e950eda59374f94a0f0c4dedd30039f4c02d --- /dev/null +++ b/Dapper/XmlHandlers.cs @@ -0,0 +1,39 @@ +using System.Data; +using System.Xml; +using System.Xml.Linq; + +namespace Dapper +{ + internal abstract class XmlTypeHandler : SqlMapper.StringTypeHandler + { + public override void SetValue(IDbDataParameter parameter, T value) + { + base.SetValue(parameter, value); + parameter.DbType = DbType.Xml; + } + } + + internal sealed class XmlDocumentHandler : XmlTypeHandler + { + protected override XmlDocument Parse(string xml) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + return doc; + } + + protected override string Format(XmlDocument xml) => xml.OuterXml; + } + + internal sealed class XDocumentHandler : XmlTypeHandler + { + protected override XDocument Parse(string xml) => XDocument.Parse(xml); + protected override string Format(XDocument xml) => xml.ToString(); + } + + internal sealed class XElementHandler : XmlTypeHandler + { + protected override XElement Parse(string xml) => XElement.Parse(xml); + protected override string Format(XElement xml) => xml.ToString(); + } +} diff --git "a/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\347\256\227\346\263\225\346\265\201\347\250\213.png" "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\347\256\227\346\263\225\346\265\201\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..982c6cfc12531ba9b027bbe3fb03284ee03411de Binary files /dev/null and "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\347\256\227\346\263\225\346\265\201\347\250\213.png" differ diff --git "a/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190312.docx" "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190312.docx" new file mode 100644 index 0000000000000000000000000000000000000000..95471275fcf026f1de3e7a2c48d34e5c9dd4a900 Binary files /dev/null and "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190312.docx" differ diff --git "a/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190403.docx" "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190403.docx" new file mode 100644 index 0000000000000000000000000000000000000000..85b14f62bfac88c0a25f068fbeff2f04565daf25 Binary files /dev/null and "b/Doc/01\351\234\200\346\261\202\346\226\207\346\241\243/\345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\351\234\200\346\261\202\350\257\204\345\256\241\344\274\232\350\256\256\347\272\252\350\246\20120190403.docx" differ diff --git "a/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/1 \350\200\225\345\234\260\350\264\250\351\207\217\347\273\274\345\220\210\347\256\241\347\220\206\345\271\263\345\217\260\346\200\273\344\275\223\350\247\204\345\210\2222019.docx" "b/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/1 \350\200\225\345\234\260\350\264\250\351\207\217\347\273\274\345\220\210\347\256\241\347\220\206\345\271\263\345\217\260\346\200\273\344\275\223\350\247\204\345\210\2222019.docx" new file mode 100644 index 0000000000000000000000000000000000000000..81ef4e364a867f479dd55bbde7508461a5c2deac Binary files /dev/null and "b/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/1 \350\200\225\345\234\260\350\264\250\351\207\217\347\273\274\345\220\210\347\256\241\347\220\206\345\271\263\345\217\260\346\200\273\344\275\223\350\247\204\345\210\2222019.docx" differ diff --git "a/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/2 \345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\345\210\235\346\255\245\350\256\276\350\256\241.docx" "b/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/2 \345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\345\210\235\346\255\245\350\256\276\350\256\241.docx" new file mode 100644 index 0000000000000000000000000000000000000000..88b0feb093b3e74de98455a6bc567653b0bd9366 Binary files /dev/null and "b/Doc/02\350\256\276\350\256\241\346\226\207\346\241\243/2 \345\271\277\350\245\277\344\270\211\350\260\203\350\200\225\345\234\260\350\264\250\351\207\217\350\257\204\344\273\267\350\275\257\344\273\266\345\210\235\346\255\245\350\256\276\350\256\241.docx" differ diff --git a/WLib.ArcGis/Data/TypeConvert.cs b/WLib.ArcGis/Data/TypeConvert.cs index c740326a6969a1f24970335d0d5f45b5ea0bd3ec..57e462058e951ca9b64e0ebc85436288509a2e92 100644 --- a/WLib.ArcGis/Data/TypeConvert.cs +++ b/WLib.ArcGis/Data/TypeConvert.cs @@ -11,6 +11,9 @@ using System; namespace WLib.ArcGis.Data { + /// + /// ArcGIS字段类型和C#数据类型等的互相转换 + /// public static class TypeConvert { /// diff --git a/WLib.UserCtrl.Dev/ArcGisCtrl/AttributeForm.cs b/WLib.UserCtrl.Dev/ArcGisCtrl/AttributeForm.cs index 3601ddabb4e289bb24a7136962f18dab66adeeb5..637a055f130f02456805d4fbf8c8ab0ce3023598 100644 --- a/WLib.UserCtrl.Dev/ArcGisCtrl/AttributeForm.cs +++ b/WLib.UserCtrl.Dev/ArcGisCtrl/AttributeForm.cs @@ -88,7 +88,7 @@ namespace WLib.UserCtrls.Dev.ArcGisCtrl WhereClause = whereClause; if (fieldNames == null) fieldNames = table.GetFieldsNames().ToArray(); - var dataTable = table.CreateDataTable(fieldNames, whereClause).SwitchColumnNameAndCaption();//创建数据表 + var dataTable = table.CreateDataTable(fieldNames, null, whereClause).SwitchColumnNameAndCaption();//创建数据表 Text = ((IDataset)Table).Name + @" - 属性表"; this.dataGridView1.ContextMenuStrip.Items[0].Visible = false; diff --git a/WLib.UserCtrl.Dev/ArcGisCtrl/MapViewer.cs b/WLib.UserCtrl.Dev/ArcGisCtrl/MapViewer.cs index 9296924165ada6f41c9c7ab0266b64bf33a17adc..3ef436860de8882a07ca211d864cc19a4447378b 100644 --- a/WLib.UserCtrl.Dev/ArcGisCtrl/MapViewer.cs +++ b/WLib.UserCtrl.Dev/ArcGisCtrl/MapViewer.cs @@ -5,13 +5,10 @@ // mdfy: None //----------------------------------------------------------------*/ -using DevExpress.XtraEditors; using ESRI.ArcGIS.Carto; -using ESRI.ArcGIS.Controls; using ESRI.ArcGIS.Geodatabase; using System; using System.Windows.Forms; -using WLib.ArcGis.Control; using WLib.ArcGis.Control.MapAssociation; using WLib.UserCtrls.ArcGisCtrl; diff --git a/WLib.UserCtrl.Dev/ChildForm/ChildFormHelper.cs b/WLib.UserCtrl.Dev/ChildForm/ChildFormHelper.cs index e030218e5ad5562954e9ab6b4e3abc19d4d04f20..8357e907552b95efd3f2529e509f696b1b96ec3f 100644 --- a/WLib.UserCtrl.Dev/ChildForm/ChildFormHelper.cs +++ b/WLib.UserCtrl.Dev/ChildForm/ChildFormHelper.cs @@ -19,7 +19,7 @@ namespace WLib.UserCtrls.Dev.ChildForm /// 打开子窗体(已存在同类窗体时,不再重复打开,而是显示已有的窗体) /// /// 已经实例化的子窗体 - /// 是否允许打开标题不同的同一类型窗体 + /// 是否打开标题不同的同一类型窗体 public static void ShowSizableForm(ChildSizableForm newform, bool openDifTextForm = false) { if (newform == null) return; @@ -48,13 +48,13 @@ namespace WLib.UserCtrls.Dev.ChildForm else//未显示过同类型窗体时,显示新窗体 { newform.Show(); - } + } } /// /// 打开子窗体(已存在同类窗体时,仍然打开新窗体) /// /// 已经实例化的子窗体 - /// 是否允许打开标题不同的同一类型窗体 + /// 是否打开标题不同的同一类型窗体 public static void ShowNewSizableForm(ChildSizableForm newform, bool openDifTextForm = false) { if (newform == null) return; @@ -65,11 +65,11 @@ namespace WLib.UserCtrls.Dev.ChildForm { forms = forms.Where(v => v.Text.Equals(newform.Text)); } - ////销毁旧窗体 - //foreach (var form in forms) - //{ - // form.Close(); form.Dispose(); - //} + //销毁旧窗体 + foreach (var form in forms) + { + form.Close(); form.Dispose(); + } } newform.Show(); } diff --git a/WLib.UserCtrl.Dev/ChildForm/DockFormControl.cs b/WLib.UserCtrl.Dev/ChildForm/DockFormControl.cs index 4e89f8705e17a25175fc7eaf65d21d0560fabda1..9b004fd4adfa71eb2b67e8159e703d803add5bc5 100644 --- a/WLib.UserCtrl.Dev/ChildForm/DockFormControl.cs +++ b/WLib.UserCtrl.Dev/ChildForm/DockFormControl.cs @@ -44,16 +44,6 @@ namespace WLib.UserCtrls.Dev.ChildForm } - /// - /// 创建、显示停靠面板并放置窗体(窗体的Show方法需另外指定),面板自动与已有面板组合成标签式面板。 - /// 不重复打开拥有相同窗体的面板。 - /// - /// 内置在停靠面板的窗体 - /// 指示面板显示/隐藏/自动隐藏的枚举 - public void AddDockFormToTabPanel(Form form, DockVisibility visibility = DockVisibility.Visible) - { - AddDockFormToTabPanel(form, 500, 600, visibility); - } /// /// 创建、显示停靠面板并放置窗体(窗体的Show方法需另外指定),面板自动与已有面板组合成标签式面板。 /// 不重复打开拥有相同窗体的面板。 @@ -62,11 +52,11 @@ namespace WLib.UserCtrls.Dev.ChildForm /// 面板宽度 /// 面板高度 /// 指示面板显示/隐藏/自动隐藏的枚举 - public void AddDockFormToTabPanel(Form form, int width, int height, DockVisibility visibility = DockVisibility.Visible) + public void AddFormToTabPanel(Form form, int width = 600, int height = 600, DockVisibility visibility = DockVisibility.Visible) { - // 查找包含指定窗体的DockPanel + // 查找包含指定窗体的DockPanel,通过Tag属性区分面板(窗体) var allPanels = dockManager1.Panels.Cast(); - var existPanel = allPanels.FirstOrDefault(v => (v.Tag != null) && v.Tag.Equals(form.Name + form.Text));//通过Tag属性区分面板(窗体) + var existPanel = allPanels.FirstOrDefault(v => (v.Tag != null) && v.Tag.Equals(form.Name + form.Text)); DockPanel dockPanel; if (existPanel == null)//不存在拥有指定窗体的DockPanel,则创建之 @@ -98,74 +88,37 @@ namespace WLib.UserCtrls.Dev.ChildForm dockPanel.Visibility = visibility; dockPanel.FloatLocation = FloatLocation; - //将窗体内置到面板工作区中 - int dockPanelWnd = dockPanel.ControlContainer.Handle.ToInt32(); - WinApi.SetParent(form.Handle.ToInt32(), dockPanelWnd); - OnResize(dockPanelWnd, form.Handle); - - //窗体自适应停靠面板大小 - dockPanel.ControlContainer.SizeChanged += (sender, e) => OnResize(dockPanelWnd, form.Handle); - - //关闭面板时关闭窗体 - dockPanel.ClosingPanel += (sender, e) => - { - if (!form.IsDisposed && IsCloseDockPanel) - form.Close(); - }; - dockPanel.ClosingPanel += dockFormPanel_ClosingPanel; - - //关闭窗体时关闭面板 - form.FormClosed += (sender, e) => - { - FloatLocation = dockPanel.FloatLocation; - dockManager1.RemovePanel(dockPanel); - }; + FormIntoDockPanel(form, dockPanel); } else { dockPanel = existPanel; } dockPanel.Show(); + dockPanel.BringToFront(); + form.Show(); } /// /// 创建、显示浮动面板并放置窗体(窗体的Show方法需另外指定)。 /// 不重复打开拥有相同窗体的面板。 /// /// 内置在停靠面板的窗体 + /// 面板显示位置 /// 面板宽度 /// 面板高度 - /// 面板显示位置 - public void AddDockFormToPanel(Form form, int width, int height, Point point) + public void AddFormToFloatPanel(Form form, Point point, int width = 600, int height = 600) { - //查找已是否存在包含指定窗体的DockPanel - var allPanels = dockManager1.Panels.Cast(); - var existPanel = allPanels.FirstOrDefault(v => (v.Tag != null) && v.Tag.Equals(form.Name + form.Text)); + //查找已是否存在包含指定窗体的DockPanel,通过Tag属性区分面板(窗体) + var existPanel = dockManager1.Panels.Cast().FirstOrDefault(v => (v.Tag != null) && v.Tag.Equals(form.Name + form.Text)); DockPanel dockPanel; if (existPanel == null) { dockPanel = dockManager1.AddPanel(FloatLocation); - dockPanel.Size = new Size(width, height); + dockPanel.FloatSize = new Size(width, height); dockPanel.Tag = form.Name + form.Text;//标记窗体,以区分不同窗体 dockPanel.Text = form.Text; - //将窗体内置到面板工作区中 - int dockPanelWnd = dockPanel.ControlContainer.Handle.ToInt32(); - WinApi.SetParent(form.Handle.ToInt32(), dockPanelWnd); - OnResize(dockPanelWnd, form.Handle); - - //窗体自适应停靠面板大小 - dockPanel.ControlContainer.SizeChanged += (sender, e) => OnResize(dockPanelWnd, form.Handle); - - //关闭面板时关闭窗体 - dockPanel.ClosingPanel += (sender, e) => - { - if (!form.IsDisposed && IsCloseDockPanel) - form.Close(); - }; - dockPanel.ClosingPanel += dockFormPanel_ClosingPanel; - - //关闭窗体时关闭面板 - form.FormClosed += (sender, e) => dockManager1.RemovePanel(dockPanel); + FormIntoDockPanel(form, dockPanel); } else { @@ -173,6 +126,37 @@ namespace WLib.UserCtrls.Dev.ChildForm } dockPanel.Show(); dockPanel.BringToFront(); + form.Show(); + } + /// + /// 将窗体内置到面板工作区中,设置窗体大小自适应、面板和窗体互相关闭 + /// + /// + /// + private void FormIntoDockPanel(Form form, DockPanel dockPanel) + { + //将窗体内置到面板工作区中 + int dockPanelWnd = dockPanel.ControlContainer.Handle.ToInt32(); + WinApi.SetParent(form.Handle.ToInt32(), dockPanelWnd); + OnResize(dockPanelWnd, form.Handle); + + //窗体自适应停靠面板大小 + dockPanel.ControlContainer.SizeChanged += (sender, e) => OnResize(dockPanelWnd, form.Handle); + + //关闭面板时关闭窗体 + dockPanel.ClosingPanel += (sender, e) => + { + if (!form.IsDisposed && IsCloseDockPanel) + form.Close(); + }; + dockPanel.ClosingPanel += dockFormPanel_ClosingPanel; + + //关闭窗体时关闭面板 + form.FormClosed += (sender, e) => + { + FloatLocation = dockPanel.FloatLocation; + dockManager1.RemovePanel(dockPanel); + }; } /// /// 当容器大小发生改变时,窗体自适应外部容器大小 @@ -196,6 +180,9 @@ namespace WLib.UserCtrls.Dev.ChildForm } + /// + /// 关闭面板时,根据IsCloseDockPanel确定隐藏面板还是销毁面板 + /// private void dockFormPanel_ClosingPanel(object sender, DockPanelCancelEventArgs e) { if (IsCloseDockPanel) diff --git a/WLib.UserCtrl.Dev/SkinCtrl/SkinOpt.cs b/WLib.UserCtrl.Dev/SkinCtrl/SkinOpt.cs new file mode 100644 index 0000000000000000000000000000000000000000..e25dd432bd9f2956be28a94232b0d3a20c1e630b --- /dev/null +++ b/WLib.UserCtrl.Dev/SkinCtrl/SkinOpt.cs @@ -0,0 +1,52 @@ +using DevExpress.LookAndFeel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WLib.UserCtrls.Dev.SkinCtrl +{ + /// + /// 界面皮肤操作 + /// + public static class SkinOpt + { + public const string 浅褐色 = "浅褐色"; + public const string Office2007蓝色 = "Office 2007 蓝色"; + public const string Office2007绿色 = "Office 2007 绿色"; + public const string Office2010蓝色 = "Office 2010 蓝色"; + public const string Office2010银色 = "Office 2010 银色"; + + + /// + /// 皮肤类型的中文名和英文名键值对 + /// + public static Dictionary SkinNameToValue; + /// + /// 界面皮肤操作 + /// + static SkinOpt() + { + DevExpress.UserSkins.OfficeSkins.Register(); + DevExpress.Skins.SkinManager.EnableFormSkins(); + DevExpress.Skins.SkinManager.EnableMdiFormSkins(); + SkinNameToValue = new Dictionary() + { + { 浅褐色, "Caramel" }, + { Office2007蓝色, "Office 2007 Blue" }, + { Office2007绿色, "Office 2007 Green" }, + { Office2010蓝色, "Office 2010 Blue" }, + { Office2010银色, "Office 2010 Silver" }, + }; + } + /// + /// 初始化并设置界面皮肤 + /// + /// + /// 皮肤类型的中文名,必须为.Keys的值之一 + public static void SetSkin(this DefaultLookAndFeel defaultLookAndFeel, string skinName) + { + defaultLookAndFeel.LookAndFeel.SkinName = SkinNameToValue[skinName]; + } + } +} diff --git a/WLib.UserCtrl.Dev/WLib.UserCtrls.Dev.csproj b/WLib.UserCtrl.Dev/WLib.UserCtrls.Dev.csproj index 6779750e76dcd921396aaf9e15b7d5315e37af6d..c857a9312bede6fbacfeb0dfc13aaec52bb6e12a 100644 --- a/WLib.UserCtrl.Dev/WLib.UserCtrls.Dev.csproj +++ b/WLib.UserCtrl.Dev/WLib.UserCtrls.Dev.csproj @@ -381,6 +381,7 @@ Resources.resx + diff --git a/WLib.UserCtrl/ArcGisCtrl/AttributeForm.cs b/WLib.UserCtrl/ArcGisCtrl/AttributeForm.cs index 0299d57ef1bb61de0f4084edaad50656eedd7724..f0bd5a3a53c89c22027fccbfd42f423881a26e4c 100644 --- a/WLib.UserCtrl/ArcGisCtrl/AttributeForm.cs +++ b/WLib.UserCtrl/ArcGisCtrl/AttributeForm.cs @@ -82,7 +82,7 @@ namespace WLib.UserCtrls.ArcGisCtrl WhereClause = whereClause; if (fieldNames == null) fieldNames = table.GetFieldsNames().ToArray(); - var dataTable = table.CreateDataTable(fieldNames, whereClause).SwitchColumnNameAndCaption();//创建数据表 + var dataTable = table.CreateDataTable(fieldNames, null, whereClause).SwitchColumnNameAndCaption();//创建数据表 Text = ((IDataset)Table).Name + @" - 属性表"; dataGridView1.ContextMenuStrip.Items[0].Visible = false; diff --git a/WLib/Data/Calculate/RegressionAnalysis.cs b/WLib/Data/Calculate/RegressionAnalysis.cs index 06afbdcf43fd4b1eb13f138c9d002ca1942c99d5..c11ef1f3e5cd7685cf80be84cc2e1eeeaae40aba 100644 --- a/WLib/Data/Calculate/RegressionAnalysis.cs +++ b/WLib/Data/Calculate/RegressionAnalysis.cs @@ -17,12 +17,12 @@ namespace WLib.Data.Calculate public static class RegressionAnalysis { /// - /// 根据样点计算一元线性回归方程 y = kx + d 的参数k和d + /// 根据统计样点计算一元线性回归方程 y = kx + d 的参数k和d /// (公式参考 https://baike.baidu.com/item/一元线性回归方程/6953911) /// /// 一元线性回归的统计样点 - /// y = kx + d 的斜率参数 - /// y = kx + d 的截距参数 + /// 回归方程 y = kx + d 的斜率参数 + /// 回归方程 y = kx + d 的截距参数 public static void UnaryLinearRegression(DcrPoint[] points, out double k, out double d) { double n = points.Length; @@ -37,12 +37,12 @@ namespace WLib.Data.Calculate d = yAvg - k * xAvg; } /// - /// 根据样点计算一元线性回归方程 y = kx + d 的参数k和d + /// 根据统计样点计算一元线性回归方程 y = kx + d 的参数k和d /// (公式参考 https://baike.baidu.com/item/一元线性回归方程/6953911) /// /// 一元线性回归的统计样点 - /// y = kx + d 的斜率参数 - /// y = kx + d 的截距参数 + /// 回归方程 y = kx + d 的斜率参数 + /// 回归方程 y = kx + d 的截距参数 public static void UnaryLinearRegression(IEnumerable points, out double k, out double d) { double n = points.Count(); diff --git a/WLib/Data/DataTableOpt.cs b/WLib/Data/DataTableOpt.cs index f2ff14a46e85754f8d5911808a4dca02663e7ed9..fb3879af415c660fe2113abd47aea10d923ad4b8 100644 --- a/WLib/Data/DataTableOpt.cs +++ b/WLib/Data/DataTableOpt.cs @@ -12,7 +12,7 @@ using System.Text; namespace WLib.Data { /// - /// DataTable的操作 + /// DataTable的创建、转置、转换等操作 /// public static class DataTableOpt { diff --git a/WLib/Data/Format/CnNumberConversion.cs b/WLib/Data/Format/CnNumberConversion.cs index b7c2ba0f5e2fcceb9102fae5695185bec62556fa..1285e6cb2ec27173e847505b995c52388365d1ed 100644 --- a/WLib/Data/Format/CnNumberConversion.cs +++ b/WLib/Data/Format/CnNumberConversion.cs @@ -17,11 +17,11 @@ namespace WLib.Data.Format /// /// "〇一二三四五六七八九" /// - public static string ChnNum = "〇一二三四五六七八九"; + private static readonly string ChnNum = "〇一二三四五六七八九"; /// /// "十百千万十百千亿" /// - public static string ChnUnit = "十百千万十百千亿"; + private static readonly string ChnUnit = "十百千万十百千亿"; /// /// 被替换的字符串 /// diff --git a/WLib/Data/ModelConvert.cs b/WLib/Data/ModelConvert.cs index 5fca286cdd0d5dfc442d0504dca8b42eee467dc7..d735539757f4a846db4696125d3b89a4fc355e36 100644 --- a/WLib/Data/ModelConvert.cs +++ b/WLib/Data/ModelConvert.cs @@ -16,7 +16,7 @@ using System.Reflection; namespace WLib.Data { /// - /// 将表格数据转换成指定类型的对象 + /// 表格数据与指定类型对象的互相转换 /// public static class ModelConvert { diff --git a/WLib/Database/DbHelper.cs b/WLib/Database/DbHelper.cs index afaf0570b056a8bf388923a80f21c27798649111..613700b7bf2e0c8a49c781eb167244d78b80f6c1 100644 --- a/WLib/Database/DbHelper.cs +++ b/WLib/Database/DbHelper.cs @@ -17,7 +17,7 @@ namespace WLib.Database /// /// 提供连接和使用SQL操作不同类型的数据库(数据源)的方法 /// - public class DbHelper : IDisposable + public class DbHelper : IDbConnection { #region 私有成员 /// @@ -172,7 +172,7 @@ namespace WLib.Database OnBeforeExcute("Get Data Set", sql); DbDataAdapter adapter = _providerFactory.CreateDataAdapter(); DbCommand dbCommand = _providerFactory.CreateCommand(); - dbCommand.Connection = Connection; + dbCommand.Connection = Connection; dbCommand.CommandTimeout = CommandTimeOut; dbCommand.CommandText = sql; if (dbParameters != null) @@ -250,19 +250,27 @@ namespace WLib.Database #endregion - /// - /// 释放对象的同时,关闭连接并释放资源 - /// - public void Dispose() - { - Close(); - } - /// - /// 关闭连接 - /// - public void Close() - { - _connection?.Close(); - } + #region 实现IDbConnection接口 + + public void Close() => _connection?.Close(); + + public int ConnectionTimeout => Connection.ConnectionTimeout; + + public string Database => Connection.Database; + + public ConnectionState State => Connection.State; + + public IDbTransaction BeginTransaction() => Connection.BeginTransaction(); + + public IDbTransaction BeginTransaction(IsolationLevel il) => Connection.BeginTransaction(il); + + public IDbCommand CreateCommand() => Connection.CreateCommand(); + + public void Dispose() => _connection?.Dispose(); + + public void ChangeDatabase(string databaseName) => Connection.ChangeDatabase(databaseName); + + public void Open() => _connection?.Close(); + #endregion } } diff --git a/WLib/DesignPattern/SingletonProvider.cs b/WLib/DesignPattern/Singleton.cs similarity index 92% rename from WLib/DesignPattern/SingletonProvider.cs rename to WLib/DesignPattern/Singleton.cs index 8664ab8d4b81327965788669aae261173fe3a482..1610b78fdc2e76831fbaa35e2b4855b3f7d5f0b5 100644 --- a/WLib/DesignPattern/SingletonProvider.cs +++ b/WLib/DesignPattern/Singleton.cs @@ -11,7 +11,7 @@ namespace WLib.DesignPattern /// 单例模式 /// /// 单例类 - public class SingletonProvider where T : class, new() + public class Singleton where T : class, new() { /// /// 获取类T的单例 @@ -26,7 +26,7 @@ namespace WLib.DesignPattern /// /// 单例模式 /// - private SingletonProvider() { } + private Singleton() { } /// diff --git a/WLib/Events/EventBinding.cs b/WLib/Events/EventBinding.cs new file mode 100644 index 0000000000000000000000000000000000000000..da8773db676ca4062b92a6188f5dcf5291876c45 --- /dev/null +++ b/WLib/Events/EventBinding.cs @@ -0,0 +1,46 @@ +using System; +using System.Reflection; + +namespace WLib.Events +{ + public static class EventBinding + { + /// + /// 移除指定对象的指定事件绑定的全部方法(移除全部事件处理方法) + /// + /// + /// + /// 事件名 + static void RemoveEvent(T TClass, string eventName) + { + Delegate[] handlers = GetObjectEventList(TClass, eventName); + if (handlers != null) + { + foreach (Delegate handler in handlers) + { + typeof(T).GetEvent(eventName).RemoveEventHandler(TClass, handler); + } + } + } + ///        + /// 获取对象事件绑定过的全部事件处理方法 + ///        + ///  对象      + ///  事件名      + ///  委托列     + public static Delegate[] GetObjectEventList(object obj, string eventName) + { + FieldInfo fieldInfo = obj.GetType().GetField(eventName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static); + if (fieldInfo == null) + return null; + + object fieldValue = fieldInfo.GetValue(obj); + if (fieldValue != null && fieldValue is Delegate) + { + Delegate objectDelegate = (Delegate)fieldValue; + return objectDelegate.GetInvocationList(); + } + return null; + } + } +} diff --git a/WLib/Model/ItemObject.cs b/WLib/Model/ItemObject.cs index 3c788d5f66b1dad27a4b72600d2fcd71d04e13d7..4866039850b5fc197d79d40503a48b9c0907d38e 100644 --- a/WLib/Model/ItemObject.cs +++ b/WLib/Model/ItemObject.cs @@ -19,7 +19,7 @@ namespace WLib.Model /// public int Index { get; set; } = -1; /// - /// 唯一值,通常为ID、编码、代码或图幅号等 + /// 唯一值编码,通常为ID、编码、代码或图幅号等 /// public string Code { get; set; } /// @@ -42,7 +42,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 public ItemObject(string code, string name) { @@ -52,7 +52,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 /// 序号 public ItemObject(string code, string name, int index) @@ -64,7 +64,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 /// 序号 /// 所在分类 diff --git a/WLib/Model/ItemObject`T.cs b/WLib/Model/ItemObject`T.cs index 029cb474b753ec4d6da76995ead0feadcaaa1d8d..6c2cfbb2f192e2ef788c7fe27eefb18465239a9b 100644 --- a/WLib/Model/ItemObject`T.cs +++ b/WLib/Model/ItemObject`T.cs @@ -21,7 +21,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 public ItemObject(string code, string name) : base(code, name) @@ -30,7 +30,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 /// 序号 public ItemObject(string code, string name, int index) @@ -40,7 +40,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 /// 序号 /// 所在分类 @@ -51,7 +51,7 @@ namespace WLib.Model /// /// 放入列表中的对象 /// - /// 唯一值(通常为ID、编码、代码或图幅号等) + /// 唯一值编码(通常为ID、编码、代码或图幅号等) /// 名称 /// 序号 /// 所在分类 diff --git a/WLib/WLib.csproj b/WLib/WLib.csproj index 37f1dc2c274d485fa8f9aaf6e18d586895d0e1e5..b252e25dbd3d2f602e1ed1d9064ac66eead43d85 100644 --- a/WLib/WLib.csproj +++ b/WLib/WLib.csproj @@ -97,9 +97,10 @@ - + + diff --git a/WLib/WLib.csproj.user b/WLib/WLib.csproj.user deleted file mode 100644 index 6cbe5889c8d2e10479ce27d57f483d22fffbae16..0000000000000000000000000000000000000000 --- a/WLib/WLib.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - ProjectFiles - - \ No newline at end of file diff --git a/WLib/WinForm/WinFormOpt.cs b/WLib/WinForm/WinFormOpt.cs index 5141e7d44cdcfa65a4f5f93c3c0193d2d1333afd..8dfefe2e3d193af7e0db67059ba6e5b990401e8c 100644 --- a/WLib/WinForm/WinFormOpt.cs +++ b/WLib/WinForm/WinFormOpt.cs @@ -5,6 +5,9 @@ // mdfy: None //----------------------------------------------------------------*/ +using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Windows.Forms; @@ -15,39 +18,53 @@ namespace WLib.WinForm /// public static class WinFormOpt { + /// + /// 从控件集合中查找与指定窗体对象类型(Form.GetType())相同的窗体 + /// + /// 控件集 + /// 要查找的窗体 + /// 是否比较窗体标题,True表示窗体标题(Form.Text)不同则不是同一窗体,False表示只比较窗体类型(Form.GetType()) + /// + public static Form QueryForm(this IEnumerable controls, Form subForm, bool compareFormText = true) + { + return compareFormText ? + controls.OfType
().FirstOrDefault(v => v.GetType() == subForm.GetType() && v.Text.Equals(subForm.Text)) : + controls.OfType().FirstOrDefault(v => v.Name.Equals(subForm.Name)); + } /// /// 显示MDI子窗体,防止重复打开同一窗体 /// /// /// 子窗体 - public static Form ShowMdiForm(this Form parentForm, Form subForm) + /// 是否比较窗体标题,True表示窗体标题(Form.Text)不同则不是同一窗体,False表示只比较窗体类型(Form.GetType()) + public static Form ShowMdiForm(this Form parentForm, Form subForm, bool compareFormText = true) { - var openedForm = parentForm.MdiChildren.FirstOrDefault(v => v.Name.Equals(subForm.Name) && v.Text.Equals(subForm.Text)); - if (openedForm == null) + var openedForm = QueryForm(parentForm.MdiChildren, subForm, compareFormText); + if (openedForm != null) { - subForm.MdiParent = parentForm; - subForm.Show(); - subForm.WindowState = FormWindowState.Maximized; - return subForm; + subForm.Close(); + subForm.Dispose(); } else { - subForm.Close(); - subForm.Dispose(); - openedForm.MdiParent = parentForm; - openedForm.Show(); - openedForm.WindowState = FormWindowState.Maximized; - return openedForm; + openedForm = subForm; } + + openedForm.MdiParent = parentForm; + openedForm.Show(); + openedForm.WindowState = FormWindowState.Maximized; + openedForm.BringToFront(); + return openedForm; } /// /// 显示独立子窗体,防止重复打开同一窗体 /// /// /// 子窗体 - public static void ShowIndependentForm(this Form parentForm, Form subForm) + /// 是否比较窗体标题,True表示窗体标题(Form.Text)不同则不是同一窗体,False表示只比较窗体类型(Form.GetType()) + public static void ShowIndependentForm(this Form parentForm, Form subForm, bool compareFormText = true) { - var openedForm = Application.OpenForms.Cast().FirstOrDefault(v => v.GetType() == subForm.GetType()); + var openedForm = QueryForm(Application.OpenForms.Cast(), subForm, compareFormText); if (openedForm != null) { if (openedForm.Visible == false) @@ -62,5 +79,87 @@ namespace WLib.WinForm subForm.BringToFront(); } } + + + /// + /// 向控件添加和显示窗体,防止重复打开同一窗体 + /// + /// + /// 要显示的窗体, + /// 若容器的控件集已存在同类型窗体则直接打开原窗体,否则在容器控件集中加入该窗体并显示该窗体 + /// 是否比较窗体标题,True表示窗体标题(Form.Text)不同则不是同一窗体,False表示只比较窗体类型(Form.GetType()) + /// + public static Form ShowSubForm(this Control parentControl, Form form, bool compareFormText = true) + { + var openedForm = QueryForm(parentControl.Controls.OfType(), form, compareFormText); + if (openedForm != null) + { + form.Close(); + form.Dispose(); + } + else + { + openedForm = form; + } + return parentControl.ShowSubForm(openedForm); + } + /// + /// 向控件添加和显示窗体,防止重复打开同一窗体 + /// + /// + /// 要显示的窗体类型,若容器的控件集已存在同类型窗体则直接打开窗体,否则在容器控件集中创建并加入该类型新窗体再显示新窗体 + /// + public static Form ShowSubForm(this Control parentControl, Type formType) + { + if (!IsType(formType, typeof(Form))) + throw new Exception($"参数{nameof(formType)}的类型不是窗体类型!"); + + var openedForm = parentControl.Controls.OfType().FirstOrDefault(v => v.GetType() == formType); + if (openedForm == null) + openedForm = (Form)Activator.CreateInstance(formType); + + return parentControl.ShowSubForm(openedForm); + } + /// + /// 向控件添加和显示窗体,窗口最大化并且隐藏窗体标题栏 + /// + /// 容器控件 + /// 显示在容器控件内的窗体 + /// + private static Form ShowSubForm(this Control parentControl, Form form) + { + _reisze = (sender, e) => { if (form != null && !form.IsDisposed) form.Size = parentControl.Size; }; + parentControl.Resize -= _reisze; + parentControl.Resize += _reisze; + + form.TopLevel = false; //窗体设置成非顶级控件 + form.FormBorderStyle = FormBorderStyle.None;//去掉窗体边框 + form.Parent = parentControl;//指定子窗体显示的容器 + form.Size = parentControl.Size; + form.Refresh(); + form.Show(); + form.BringToFront(); + return form; + } + /// + /// 容器控件的控件大小调整事件 + /// + private static EventHandler _reisze = null; + + /// + /// 判断类型是否为指定类型或继承自指定类型 + /// + /// 被判断的类型 + /// 指定类型,表示被判断的类型是否继承自该类型 + /// + private static bool IsType(Type type, Type compareType) + { + if (type == compareType) + return true; + else if (type == null) + return false; + else + return IsType(type.BaseType, compareType); + } } } diff --git a/YYGDZL.Business.Base/App.config b/YYGDZL.Business.Base/App.config new file mode 100644 index 0000000000000000000000000000000000000000..624573f80176d7a8195dc95635193df7bb6e405d --- /dev/null +++ b/YYGDZL.Business.Base/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/YYGDZL.Business.Base/Command/ICommand.cs b/YYGDZL.Business.Base/Command/ICommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..ec4f53a45554fcc38862ec949f25785224333b30 --- /dev/null +++ b/YYGDZL.Business.Base/Command/ICommand.cs @@ -0,0 +1,33 @@ +namespace YYGDZL.Business.Base.Command +{ + /// + /// 表示一项功能命令 + /// + public interface ICommand + { + /// + /// 名称 + /// + string Name { get; set; } + /// + /// 标题 + /// + string Caption { get; set; } + /// + /// 功能分组 + /// + string Category { get; set; } + /// + /// 提示信息 + /// + string ToolTip { get; set; } + /// + /// 功能描述 + /// + string Description { get; set; } + /// + /// 单击事件 + /// + void OnClick(); + } +} diff --git a/YYGDZL.Business.Base/Config/ConfigFactory.cs b/YYGDZL.Business.Base/Config/ConfigFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..98064d4030b08506ec2879912c1df5f6f26708cf --- /dev/null +++ b/YYGDZL.Business.Base/Config/ConfigFactory.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using YYGDZL.Business.Base.Config.Params; +using System.IO; + +namespace YYGDZL.Business.Base.Config +{ + /// + /// 耕地质量软件配置 + /// + public class ConfigFactory + { + public static string ParamsDir = AppDomain.CurrentDomain.BaseDirectory + "\\Config\\ParamsConfig"; + public static string CurParamsPath = ParamsDir + "\\三调参数方案\\广西三调.db"; + + /// + /// 全部评价参数方案 + /// + public static List ParamsPlanColls { get; private set; } + /// + /// 当前使用的评价参数方案 + /// + public static ParamsConfigsPlan CurParamsPlan { get; private set; } + + + /// + /// 耕地质量软件配置 + /// + static ConfigFactory() + { + ParamsPlanColls = new List(); + ParamsPlanColls.Add(ParamsConfigsManager.GetSDPlans(Path.Combine(ParamsDir, "三调参数方案"))); + ParamsPlanColls.Add(ParamsConfigsManager.GetNDBGPlans(Path.Combine(ParamsDir, "年度变更参数方案"))); + + foreach (var paramsPlanColl in ParamsPlanColls) + { + CurParamsPlan = paramsPlanColl.FirstOrDefault(v => v.FilePath == CurParamsPath); + if (CurParamsPlan != null) + break; + } + } + } +} diff --git a/YYGDZL.Business.Base/Config/IGdConfig.cs b/YYGDZL.Business.Base/Config/IGdConfig.cs new file mode 100644 index 0000000000000000000000000000000000000000..e051341ad86633b0ea754ed4e7690362cda10f89 --- /dev/null +++ b/YYGDZL.Business.Base/Config/IGdConfig.cs @@ -0,0 +1,9 @@ +namespace YYGDZL.Business.Base +{ + /// + /// 表示耕地质量软件的配置 + /// + public interface IGdConfig + { + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\345\244\215\347\247\215\347\261\273\345\236\213.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\345\244\215\347\247\215\347\261\273\345\236\213.cs" new file mode 100644 index 0000000000000000000000000000000000000000..ec9430aa9d5203d9e651be39877f5a08d678b3fd --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\345\244\215\347\247\215\347\261\273\345\236\213.cs" @@ -0,0 +1,14 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_复种类型 + { + [Key] + public int 编号 { get; set; } + + public string 代码 { get; set; } + + public string 复种类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\233\264\346\226\260\347\261\273\345\236\213.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\233\264\346\226\260\347\261\273\345\236\213.cs" new file mode 100644 index 0000000000000000000000000000000000000000..a855e3d429e1bf2c49c2bb0203c2f533c0ec21a9 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\233\264\346\226\260\347\261\273\345\236\213.cs" @@ -0,0 +1,14 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_更新类型 + { + [Key] + public int 编号 { get; set; } + + public string 代码 { get; set; } + + public string 更新类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246.cs" new file mode 100644 index 0000000000000000000000000000000000000000..5fcfb40e24c634a5fba6f87fc2cb981674de4fd5 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246.cs" @@ -0,0 +1,19 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_标准耕作制度 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 行政区代码 { get; set; } + + public string 地类编码 { get; set; } + + public string 标准耕作制度 { get; set; } + + public string 复种类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246\344\275\234\347\211\251\345\220\215\347\247\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246\344\275\234\347\211\251\345\220\215\347\247\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..07a78427293eadd1c03faa660411c6bc07cf4fdf --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\346\240\207\345\207\206\350\200\225\344\275\234\345\210\266\345\272\246\344\275\234\347\211\251\345\220\215\347\247\260.cs" @@ -0,0 +1,18 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_标准耕作制度作物名称 + { + [Key] + public int 编号 { get; set; } + + public string 代码 { get; set; } + + public string 作物名称 { get; set; } + + public string 拼音缩写 { get; set; } + + public string 拼音全拼 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272.cs" new file mode 100644 index 0000000000000000000000000000000000000000..85b058972a558fa8c84fdeba44a66a4b3a0e97ec --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272.cs" @@ -0,0 +1,16 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_行政区 + { + [Key] + public int 编码 { get; set; } + + public string 行政区代码 { get; set; } + + public string 行政区名称 { get; set; } + + public string 行政区级别 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272\345\235\220\346\240\207\347\263\273.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272\345\235\220\346\240\207\347\263\273.cs" new file mode 100644 index 0000000000000000000000000000000000000000..f106d8af5a2e415f7acbc556422b4ae1d4222ff8 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\241\214\346\224\277\345\214\272\345\235\220\346\240\207\347\263\273.cs" @@ -0,0 +1,15 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_行政区坐标系 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 行政区代码 { get; set; } + + public string 坐标系名称 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\257\204\344\273\267\345\233\240\347\264\240\344\273\243\347\240\201.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\257\204\344\273\267\345\233\240\347\264\240\344\273\243\347\240\201.cs" new file mode 100644 index 0000000000000000000000000000000000000000..a5783fdcdfb8566746ff4375ab7fdb1a8c244123 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\344\273\243\347\240\201_\350\257\204\344\273\267\345\233\240\347\264\240\344\273\243\347\240\201.cs" @@ -0,0 +1,21 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 代码_评价因素代码 + { + [Key] + public int 编号 { get; set; } + + public string 代码 { get; set; } + + public int 级别 { get; set; } + + public string 分类代码说明 { get; set; } + + public string 备注 { get; set; } + + [ForeignKey(nameof(区域_分等因素))] + public string 分等因素代码 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\210\206\347\255\211\345\233\240\347\264\240.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\210\206\347\255\211\345\233\240\347\264\240.cs" new file mode 100644 index 0000000000000000000000000000000000000000..cdb370120147d8bf5e1765a3179a6ef5eefbdc96 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\210\206\347\255\211\345\233\240\347\264\240.cs" @@ -0,0 +1,24 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 区域_分等因素 + { + [Key] + public int 编号 { get; set; } + + public string 分等因素代码 { get; set; } + + public string 分等因素名称 { get; set; } + + public string 字段类型 { get; set; } + + public int 字段长度 { get; set; } + + public double? 值域下限 { get; set; } + + public double? 值域上限 { get; set; } + + public bool 是否可选 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\234\260\347\211\251\347\261\273\345\236\213.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\234\260\347\211\251\347\261\273\345\236\213.cs" new file mode 100644 index 0000000000000000000000000000000000000000..3576f21cee78727b1367ae7b1d7ab0de3b1be583 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\345\234\260\347\211\251\347\261\273\345\236\213.cs" @@ -0,0 +1,16 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 区域_地物类型 + { + [Key] + public int 编号 { get; set; } + + public string 地类编码 { get; set; } + + public string 地类名称 { get; set; } + + public string 拼音缩写 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\347\255\211\346\214\207\346\225\260\350\275\254\346\215\242\345\217\202\346\225\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\347\255\211\346\214\207\346\225\260\350\275\254\346\215\242\345\217\202\346\225\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..52b3f7bb935992982f872f3e0d7c205ec7dad6c1 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\214\272\345\237\237_\347\255\211\346\214\207\346\225\260\350\275\254\346\215\242\345\217\202\346\225\260.cs" @@ -0,0 +1,27 @@ +using Dapper; + + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 区域_等指数转换参数 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 省行政区代码 { get; set; } + + public double 参数ZM { get; set; } + + public double 参数ZN { get; set; } + + public double 参数LM { get; set; } + + public double 参数LN { get; set; } + + public double 参数JM { get; set; } + + public double 参数JN { get; set; } + } +} + \ No newline at end of file diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\210\206\347\255\211\345\233\240\347\264\240\346\235\203\351\207\215.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\210\206\347\255\211\345\233\240\347\264\240\346\235\203\351\207\215.cs" new file mode 100644 index 0000000000000000000000000000000000000000..eed5fe0a1d722181879adf5715201f606548a9b8 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\210\206\347\255\211\345\233\240\347\264\240\346\235\203\351\207\215.cs" @@ -0,0 +1,21 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_分等因素权重 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(参数_指标区类型))] + public string 指标区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 作物名称 { get; set; } + + [ForeignKey(nameof(区域_分等因素))] + public string 分等因素代码 { get; set; } + + public double 分等因素权重 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\217\202\346\225\260\345\210\206\347\261\273\346\217\217\350\277\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\217\202\346\225\260\345\210\206\347\261\273\346\217\217\350\277\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..9e832e2a08bf2f5e1fe71d5374f4f41e656c1f52 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\217\202\346\225\260\345\210\206\347\261\273\346\217\217\350\277\260.cs" @@ -0,0 +1,14 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_参数分类描述 + { + [Key] + public int 编号 { get; set; } + public string 参数项 { get; set; } + public string 参数分组 { get; set; } + public string 参数描述 { get; set; } + public string 参数方案 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\233\240\347\264\240\345\210\206\345\200\274.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\233\240\347\264\240\345\210\206\345\200\274.cs" new file mode 100644 index 0000000000000000000000000000000000000000..d0e1ee34d92434afd43345a9b357aa1a6561d09c --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\233\240\347\264\240\345\210\206\345\200\274.cs" @@ -0,0 +1,25 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_因素分值 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(参数_指标区类型))] + public string 指标区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 指定作物名称 { get; set; } + + public double 指定作物分值 { get; set; } + + [ForeignKey(nameof(区域_分等因素))] + public string 分等因素代码 { get; set; } + + public string 值域下限 { get; set; } + + public string 值域上限 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\237\272\345\207\206\344\275\234\347\211\251.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\237\272\345\207\206\344\275\234\347\211\251.cs" new file mode 100644 index 0000000000000000000000000000000000000000..757e3729fcda87b282880b1e099883b44e3577b7 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\345\237\272\345\207\206\344\275\234\347\211\251.cs" @@ -0,0 +1,18 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_基准作物 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(参数_指标区类型))] + public string 指标区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 基准作物名称 { get; set; } + + public double 基准作物单产 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\211\271\350\200\214\346\234\252\347\224\250\351\235\242\347\247\257.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\211\271\350\200\214\346\234\252\347\224\250\351\235\242\347\247\257.cs" new file mode 100644 index 0000000000000000000000000000000000000000..764771defa7270f6612be2c5ba5d496f3d18caa0 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\211\271\350\200\214\346\234\252\347\224\250\351\235\242\347\247\257.cs" @@ -0,0 +1,18 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_批而未用面积 + { + [Key] + public int 编号 { get; set; } + + public string 行政区代码 { get; set; } + + public string 地类编码 { get; set; } + + public string 批而为用面积 { get; set; } + + public string 类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\344\272\247\351\207\217\347\263\273\346\225\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\344\272\247\351\207\217\347\263\273\346\225\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..dc118fc76c2078be37093b5b672c5e692f70cac0 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\344\272\247\351\207\217\347\263\273\346\225\260.cs" @@ -0,0 +1,20 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_指定作物产量系数 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(参数_指标区类型))] + public string 指标区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 作物名称 { get; set; } + + public double 作物常量 { get; set; } + + public double 产量比系数 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\345\210\251\347\224\250\347\273\217\346\265\216\347\255\211\347\263\273\346\225\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\345\210\251\347\224\250\347\273\217\346\265\216\347\255\211\347\263\273\346\225\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..1423b4dc5e159ed65d3f1af5b569ff538a9df097 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\345\210\251\347\224\250\347\273\217\346\265\216\347\255\211\347\263\273\346\225\260.cs" @@ -0,0 +1,26 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_指定作物利用经济等系数 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(参数_指标区类型))] + public string 指标区代码 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 行政区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 作物名称 { get; set; } + + [ForeignKey(nameof(区域_地物类型))] + public string 地类编码 { get; set; } + + public string 系数类型 { get; set; } + + public double 系数值 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\346\275\234\345\212\233\346\214\207\346\225\260.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\346\275\234\345\212\233\346\214\207\346\225\260.cs" new file mode 100644 index 0000000000000000000000000000000000000000..5b386492ff4420203ce0580e6906692e8ade0815 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\345\256\232\344\275\234\347\211\251\346\275\234\345\212\233\346\214\207\346\225\260.cs" @@ -0,0 +1,20 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_指定作物潜力指数 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 行政区代码 { get; set; } + + [ForeignKey(nameof(代码_标准耕作制度作物名称))] + public string 作物名称 { get; set; } + + public double? 光温潜力指数 { get; set; } + + public double? 气候潜力指数 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\346\240\207\345\214\272\347\261\273\345\236\213.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\346\240\207\345\214\272\347\261\273\345\236\213.cs" new file mode 100644 index 0000000000000000000000000000000000000000..bdfeb870eaf69789732339cf16416327e89caa8f --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\214\207\346\240\207\345\214\272\347\261\273\345\236\213.cs" @@ -0,0 +1,18 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_指标区类型 + { + [Key] + public int 编号 { get; set; } + + public string 指标区代码 { get; set; } + + public string 指标区级别 { get; set; } + + public string 指标区名称 { get; set; } + + public string 指标区类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\243\200\346\237\245\351\230\210\345\200\274.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\243\200\346\237\245\351\230\210\345\200\274.cs" new file mode 100644 index 0000000000000000000000000000000000000000..5d8779f8c823b6ed075725b43feca9355f3a312b --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\346\243\200\346\237\245\351\230\210\345\200\274.cs" @@ -0,0 +1,18 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_检查阈值 + { + [Key] + public int 编号 { get; set; } + + public string 检查名称 { get; set; } + + public string 检查项目 { get; set; } + + public float 阈值 { get; set; } + + public string 单位 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\347\255\211\345\210\253\345\210\222\345\210\206.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\347\255\211\345\210\253\345\210\222\345\210\206.cs" new file mode 100644 index 0000000000000000000000000000000000000000..e5a088e5e7b752fe4e8a9137ad1e35ab9c7e2200 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\347\255\211\345\210\253\345\210\222\345\210\206.cs" @@ -0,0 +1,20 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_等别划分 + { + [Key] + public int 编号 { get; set; } + + public string 等别名称 { get; set; } + + public int 等别 { get; set; } + + public double 等指数下限 { get; set; } + + public double 等指数上限 { get; set; } + + public string 等别类型 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\350\200\225\345\234\260\345\217\230\345\214\226\351\235\242\347\247\257.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\350\200\225\345\234\260\345\217\230\345\214\226\351\235\242\347\247\257.cs" new file mode 100644 index 0000000000000000000000000000000000000000..3c4424cc31094548c3da1b9e6261dd11903329ec --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\345\217\202\346\225\260_\350\200\225\345\234\260\345\217\230\345\214\226\351\235\242\347\247\257.cs" @@ -0,0 +1,22 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 参数_耕地变化面积 + { + [Key] + public int 编号 { get; set; } + + public string 年份 { get; set; } + + [ForeignKey(nameof(代码_行政区))] + public string 行政区代码 { get; set; } + + [ForeignKey(nameof(区域_地物类型))] + public string 地类编码 { get; set; } + + public string 耕地类型 { get; set; } + + public double 耕地变化面积 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\233\276\345\261\202\350\247\204\350\214\203.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\233\276\345\261\202\350\247\204\350\214\203.cs" new file mode 100644 index 0000000000000000000000000000000000000000..a6a4ea78b2eb6e20c9214e2a2623c325b908c39d --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\233\276\345\261\202\350\247\204\350\214\203.cs" @@ -0,0 +1,20 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 规范_图层规范 + { + [Key] + public int 编号 { get; set; } + + public int 表编号 { get; set; } + + public string 表名称 { get; set; } + + public string 表中文名称 { get; set; } + + public bool 是否必填 { get; set; } + + public bool 是否检查多余字段 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\255\227\346\256\265\350\256\276\347\275\256\350\247\204\350\214\203.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\255\227\346\256\265\350\256\276\347\275\256\350\247\204\350\214\203.cs" new file mode 100644 index 0000000000000000000000000000000000000000..33cdf9beae3898bf2a685525b78609e63363c2b4 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\345\255\227\346\256\265\350\256\276\347\275\256\350\247\204\350\214\203.cs" @@ -0,0 +1,45 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 规范_字段设置规范 + { + [Key] + public int 编号 { get; set; } + + [ForeignKey(nameof(规范_图层规范))] + public int 表编号 { get; set; } + + public int 字段编号 { get; set; } + + public string 字段名称 { get; set; } + + public string 字段中文名称 { get; set; } + + public string 字段类型 { get; set; } + + public int 字段长度 { get; set; } + + public int 字段精度 { get; set; } + + public double? 字段值下限 { get; set; } + + public double? 字段值上限 { get; set; } + + public string 字符串约束 { get; set; } + + public string 约束说明 { get; set; } + + public string 外键表 { get; set; } + + public string 外键字段 { get; set; } + + public bool 是否必填 { get; set; } + + public bool 是否为主键 { get; set; } + + public bool 是否唯一 { get; set; } + + public string 备注 { get; set; } + } +} diff --git "a/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\346\225\260\346\215\256\345\272\223\350\247\204\350\214\203.cs" "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\346\225\260\346\215\256\345\272\223\350\247\204\350\214\203.cs" new file mode 100644 index 0000000000000000000000000000000000000000..34ec6b863b1bf58b24b7f8330e5f008d1fc2f943 --- /dev/null +++ "b/YYGDZL.Business.Base/Config/Params/Model/\350\247\204\350\214\203_\346\225\260\346\215\256\345\272\223\350\247\204\350\214\203.cs" @@ -0,0 +1,16 @@ +using Dapper; + +namespace YYGDZL.Business.Base.Config.Params.Model +{ + public class 规范_数据库规范 + { + [Key] + public int 编号 { get; set; } + + public string 数据库名称 { get; set; } + + public string 数据库中文名称 { get; set; } + + public string 类型 { get; set; } + } +} diff --git a/YYGDZL.Business.Base/Config/Params/ParamsConfig.cs b/YYGDZL.Business.Base/Config/Params/ParamsConfig.cs new file mode 100644 index 0000000000000000000000000000000000000000..c2eb2a9e8eddb5c16b34ec0a39add8a322c7093a --- /dev/null +++ b/YYGDZL.Business.Base/Config/Params/ParamsConfig.cs @@ -0,0 +1,82 @@ +using Dapper; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using YYGDZL.Business.Base.Config.Params.Model; + +namespace YYGDZL.Business.Base.Config.Params +{ + /// + /// 参数配置 + /// + public class ParamsConfig : IGdConfig + { + #region 私有成员(表格实体) + private List<参数_等别划分> _等别划分; + private List<参数_分等因素权重> _分等因素权重; + private List<参数_耕地变化面积> _耕地变化面积; + private List<参数_检查阈值> _检查阈值; + private List<参数_批而未用面积> _批而未用面积; + private List<参数_因素分值> _因素分值; + private List<参数_指标区类型> _指标区类型; + private List<参数_指定作物产量系数> _指定作物产量系数; + private List<参数_指定作物利用经济等系数> _指定作物利用经济等系数; + private List<参数_指定作物潜力指数> _指定作物潜力指数; + private List<代码_标准耕作制度> _标准耕作制度; + private List<代码_标准耕作制度作物名称> _标准耕作制度作物名称; + private List<代码_复种类型> _复种类型; + private List<代码_更新类型> _更新类型; + private List<代码_行政区> _行政区; + private List<代码_行政区坐标系> _行政区坐标系; + private List<代码_评价因素代码> _评价因素代码; + private List<规范_数据库规范> _数据库规范; + private List<规范_图层规范> _图层规范; + private List<规范_字段设置规范> _字段设置规范; + private List<区域_等指数转换参数> _等指数转换参数; + private List<区域_地物类型> _地物类型; + private List<区域_分等因素> _分等因素; + #endregion + + + #region 公有属性(表格实体) + public List<参数_等别划分> 等别划分 => _等别划分 ?? Connection.SimpleQuery<参数_等别划分>().ToList(); + public List<参数_分等因素权重> 分等因素权重 => _分等因素权重 ?? Connection.SimpleQuery<参数_分等因素权重>().ToList(); + public List<参数_耕地变化面积> 耕地变化面积 => _耕地变化面积 ?? Connection.SimpleQuery<参数_耕地变化面积>().ToList(); + public List<参数_检查阈值> 检查阈值 => _检查阈值 ?? Connection.SimpleQuery<参数_检查阈值>().ToList(); + public List<参数_批而未用面积> 批而未用面积 => _批而未用面积 ?? Connection.SimpleQuery<参数_批而未用面积>().ToList(); + public List<参数_因素分值> 因素分值 => _因素分值 ?? Connection.SimpleQuery<参数_因素分值>().ToList(); + public List<参数_指标区类型> 指标区类型 => _指标区类型 ?? Connection.SimpleQuery<参数_指标区类型>().ToList(); + public List<参数_指定作物产量系数> 指定作物产量系数 => _指定作物产量系数 ?? Connection.SimpleQuery<参数_指定作物产量系数>().ToList(); + public List<参数_指定作物利用经济等系数> 指定作物利用经济等系数 => _指定作物利用经济等系数 ?? Connection.SimpleQuery<参数_指定作物利用经济等系数>().ToList(); + public List<参数_指定作物潜力指数> 指定作物潜力指数 => _指定作物潜力指数 ?? Connection.SimpleQuery<参数_指定作物潜力指数>().ToList(); + public List<代码_标准耕作制度> 标准耕作制度 => _标准耕作制度 ?? Connection.SimpleQuery<代码_标准耕作制度>().ToList(); + public List<代码_标准耕作制度作物名称> 标准耕作制度作物名称 => _标准耕作制度作物名称 ?? Connection.SimpleQuery<代码_标准耕作制度作物名称>().ToList(); + public List<代码_复种类型> 复种类型 => _复种类型 ?? Connection.SimpleQuery<代码_复种类型>().ToList(); + public List<代码_更新类型> 更新类型 => _更新类型 ?? Connection.SimpleQuery<代码_更新类型>().ToList(); + public List<代码_行政区> 行政区 => _行政区 ?? Connection.SimpleQuery<代码_行政区>().ToList(); + public List<代码_行政区坐标系> 行政区坐标系 => _行政区坐标系 ?? Connection.SimpleQuery<代码_行政区坐标系>().ToList(); + public List<代码_评价因素代码> 评价因素代码 => _评价因素代码 ?? Connection.SimpleQuery<代码_评价因素代码>().ToList(); + public List<规范_数据库规范> 数据库规范 => _数据库规范 ?? Connection.SimpleQuery<规范_数据库规范>().ToList(); + public List<规范_图层规范> 图层规范 => _图层规范 ?? Connection.SimpleQuery<规范_图层规范>().ToList(); + public List<规范_字段设置规范> 字段设置规范 => _字段设置规范 ?? Connection.SimpleQuery<规范_字段设置规范>().ToList(); + public List<区域_等指数转换参数> 等指数转换参数 => _等指数转换参数 ?? Connection.SimpleQuery<区域_等指数转换参数>().ToList(); + public List<区域_地物类型> 地物类型 => _地物类型 ?? Connection.SimpleQuery<区域_地物类型>().ToList(); + public List<区域_分等因素> 分等因素 => _分等因素 ?? Connection.SimpleQuery<区域_分等因素>().ToList(); + #endregion + + + /// + /// 参数配置库的数据库连接 + /// + public IDbConnection Connection { get; set; } + /// + /// 参数配置 + /// + public ParamsConfig() { } + /// + /// 参数配置 + /// + /// 参数配置库的数据库连接 + public ParamsConfig(IDbConnection connection) => Connection = connection; + } +} diff --git a/YYGDZL.Business.Base/Config/Params/ParamsConfigsClaasify.cs b/YYGDZL.Business.Base/Config/Params/ParamsConfigsClaasify.cs new file mode 100644 index 0000000000000000000000000000000000000000..599f84e0c165aa3d334e69aa3447c2f9a05fbdad --- /dev/null +++ b/YYGDZL.Business.Base/Config/Params/ParamsConfigsClaasify.cs @@ -0,0 +1,35 @@ +namespace YYGDZL.Business.Base.Config.Params +{ + /// + /// 评价参数类别 + /// (一般有以下类别:评价指数、等别划分、作物分区、行政区划等) + /// + public class ParamsConfigsClaasify + { + /// + /// 类别名称(例如评价指数、等别划分、作物分区、行政区划) + /// + public string ClassifyName { get; set; } + /// + /// 关联视图,即关联的窗体或网页(例如) + /// + public string RelationView { get; set; } + /// + /// 该类别包含的参数项 + /// + public string[] ParamsItems { get; set; } + /// + /// 评价参数类别 + /// (一般有以下类别:评价指数、等别划分、作物分区、行政区划等) + /// + /// 类别名称(例如评价指数、等别划分、作物分区、行政区划) + /// 关联视图,即关联的窗体或网页(例如) + /// 该类别包含的参数项 + public ParamsConfigsClaasify(string classifyName, string relationView, string[] paramsItems) + { + ClassifyName = classifyName; + RelationView = relationView; + ParamsItems = paramsItems; + } + } +} \ No newline at end of file diff --git a/YYGDZL.Business.Base/Config/Params/ParamsConfigsManager.cs b/YYGDZL.Business.Base/Config/Params/ParamsConfigsManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..9df5c16cd263b11a6d95e8dd6155bdc5ba4d7a55 --- /dev/null +++ b/YYGDZL.Business.Base/Config/Params/ParamsConfigsManager.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YYGDZL.Business.Base.Config.Params +{ + /// + /// 评价参数管理 + /// + public class ParamsConfigsManager + { + public const string FDYS = "分等因素"; + public const string FDYSQZ = "分等因素权重"; + public const string FDYSZLF = "分等因素质量分"; + public const string GWQLZS = "光温潜力指数"; + public const string QHQLZS = "气候潜力指数"; + public const string ZWCLBXS = "作物产量比系数"; + public const string ZWLYDXS = "作物利用等系数"; + public const string ZWJJDXS = "作物经济等系数"; + public const string SDDB = "三调等别"; + public const string SJDB = "省级等别"; + public const string GJDB = "国家等别"; + public const string GZZDFQ = "耕作制度分区"; + public const string BZGZZD = "标准耕作制度"; + public const string ZDZW = "指定作物"; + public const string JZZW = "基准作物"; + public const string XZQDM = "行政区代码"; + + public const string Group_PJZS = "评价指数"; + public const string Group_DBHF = "等别划分"; + public const string Group_ZWFQ = "作物分区"; + public const string Group_XZQH = "行政区划"; + public const string PlanType_SD = "三调参数方案"; + public const string PlanType_NDBG = "年度变更参数方案"; + + + /// + /// 获取三调参数方案内容 + /// + /// + /// + public static ParamsConfigsPlanColl GetSDPlans(string paramsDir) + { + var plans = Directory.GetFiles(paramsDir).Select(filePath => new ParamsConfigsPlan(filePath)).ToArray(); + foreach (var plan in plans) + { + plan.Classifies.AddRange(new[] + { + new ParamsConfigsClaasify(Group_PJZS, "", new[] { FDYS, FDYSQZ, FDYSZLF, GWQLZS, QHQLZS, ZWCLBXS, ZWLYDXS, ZWJJDXS }), + new ParamsConfigsClaasify(Group_DBHF, "", new[] { SDDB }), + new ParamsConfigsClaasify(Group_ZWFQ, "", new[] { GZZDFQ, BZGZZD, ZDZW, JZZW }), + new ParamsConfigsClaasify(Group_XZQH, "", new[] { XZQDM }), + }); + } + return new ParamsConfigsPlanColl(PlanType_SD, plans); + } + /// + /// 获取年度变更参数方案内容 + /// + public static ParamsConfigsPlanColl GetNDBGPlans(string paramsDir) + { + var plans = Directory.GetFiles(paramsDir).Select(filePath => new ParamsConfigsPlan(filePath)).ToArray(); + foreach (var plan in plans) + { + plan.Classifies.AddRange(new[] + { + new ParamsConfigsClaasify(Group_PJZS, "", new[] { FDYS, FDYSQZ, FDYSZLF, GWQLZS, QHQLZS, ZWCLBXS, ZWLYDXS, ZWJJDXS }), + new ParamsConfigsClaasify(Group_DBHF, "", new[] { SJDB, GJDB }), + new ParamsConfigsClaasify(Group_ZWFQ, "", new[] { GZZDFQ, BZGZZD, ZDZW, JZZW }), + new ParamsConfigsClaasify(Group_XZQH, "", new[] { XZQDM }), + }); + } + return new ParamsConfigsPlanColl(PlanType_NDBG, plans); + } + } +} diff --git a/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlan.cs b/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlan.cs new file mode 100644 index 0000000000000000000000000000000000000000..bdfc8c5f5cebb3b4f58e9444e9a00cd28b4b02c2 --- /dev/null +++ b/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlan.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.IO; + +namespace YYGDZL.Business.Base.Config.Params +{ + /// + /// 评价参数方案 + /// + public class ParamsConfigsPlan + { + /// + /// 保存方案的文件的位置 + /// + public string FilePath { get; } + /// + /// 方案名称 + /// + public string Name => Path.GetFileNameWithoutExtension(this.FilePath); + /// + /// 评价参数方案包含的评价参数类别 + /// + public List Classifies { get; } = new List(); + + + /// + /// 评价参数方案 + /// + /// 保存方案的文件的位置 + /// 方案类型 + public ParamsConfigsPlan(string planFilePath) => this.FilePath = planFilePath; + + + } +} diff --git a/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlanColl.cs b/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlanColl.cs new file mode 100644 index 0000000000000000000000000000000000000000..90ceb203b03bd0e335f5eb3e0003f630810eea30 --- /dev/null +++ b/YYGDZL.Business.Base/Config/Params/ParamsConfigsPlanColl.cs @@ -0,0 +1,38 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YYGDZL.Business.Base.Config.Params +{ + /// + /// 评价参数方案集合 + /// + public class ParamsConfigsPlanColl : List + { + /// + /// 方案类型(例如:三调方案、年度变更方案) + /// + public string PlanType { get; } + + + /// + /// 获取指定名称的方案 + /// + /// 方案名称 + /// + public ParamsConfigsPlan this[string planName]=> this.FirstOrDefault(v => v.Name == planName); + /// + /// 评价参数方案集合 + /// + /// 方案类型(例如:三调方案、年度变更方案) + public ParamsConfigsPlanColl(string planType) => this.PlanType = planType; + /// + /// 评价参数方案集合 + /// + /// 方案类型(例如:三调方案、年度变更方案) + public ParamsConfigsPlanColl(string planType, IEnumerable paramsConfigsPlans) : this(planType) => AddRange(paramsConfigsPlans); + } +} diff --git a/YYGDZL.Business.Base/Properties/AssemblyInfo.cs b/YYGDZL.Business.Base/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..a4a3dbcf35619e130a38d932c57f5efd7da63e9f --- /dev/null +++ b/YYGDZL.Business.Base/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("YYGDZL.Business.Base")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("YYGDZL.Business.Base")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("0c939673-21a7-468f-8f83-aa1227cbc558")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/YYGDZL.Business.Base/Properties/Resources.Designer.cs b/YYGDZL.Business.Base/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..714500433d6e23346cb574e055f77058729d00da --- /dev/null +++ b/YYGDZL.Business.Base/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本: 4.0.30319.42000 +// +// 对此文件的更改可能导致不正确的行为,如果 +// 重新生成代码,则所做更改将丢失。 +// +//------------------------------------------------------------------------------ + +namespace YYGDZL.Business.Base.Properties +{ + + + /// + /// 强类型资源类,用于查找本地化字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// 返回此类使用的缓存 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("YYGDZL.Business.Base.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 覆盖当前线程的 CurrentUICulture 属性 + /// 使用此强类型的资源类的资源查找。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/YYGDZL.Business.Base/Properties/Resources.resx b/YYGDZL.Business.Base/Properties/Resources.resx new file mode 100644 index 0000000000000000000000000000000000000000..af7dbebbacef595e3089c01c05671016c21a8304 --- /dev/null +++ b/YYGDZL.Business.Base/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/YYGDZL.Business.Base/Properties/Settings.Designer.cs b/YYGDZL.Business.Base/Properties/Settings.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..8a92cfd552e70b51c29a7de8fe09001e2144d97b --- /dev/null +++ b/YYGDZL.Business.Base/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace YYGDZL.Business.Base.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/YYGDZL.Business.Base/Properties/Settings.settings b/YYGDZL.Business.Base/Properties/Settings.settings new file mode 100644 index 0000000000000000000000000000000000000000..39645652af62950ebf3b28ec3a5400dcec30b1c4 --- /dev/null +++ b/YYGDZL.Business.Base/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/YYGDZL.Business.Base/YYGDZL.Business.Base.csproj b/YYGDZL.Business.Base/YYGDZL.Business.Base.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f50c654f387547eff58727ad446a6ea10958cb96 --- /dev/null +++ b/YYGDZL.Business.Base/YYGDZL.Business.Base.csproj @@ -0,0 +1,166 @@ + + + + + Debug + AnyCPU + {0C939673-21A7-468F-8F83-AA1227CBC558} + Library + YYGDZL.Business.Base + YYGDZL.Business.Base + v4.5 + 512 + true + + + AnyCPU + true + full + false + ..\Apps\ + DEBUG;TRACE + prompt + 4 + + + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + False + ..\DLL\System.Data.SQLite.dll + + + False + ..\DLL\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {b69ea382-d83f-481f-88b9-f0ac9dbce928} + Dapper + + + {0e418c82-3534-4604-bee4-4e5bd0bb926d} + WLib.ArcGis + + + {32fb07dd-7400-4011-bebc-72ed503b4911} + WLib.Envir + + + {aeb9c1be-96cf-42f5-ab08-7a45817cf906} + WLib.Files.Excel + + + {140b142f-0a73-4561-91ca-2ad98cf38572} + WLib.Files.Word + + + {5ac4fbca-7dc4-49ad-b3ef-f5a7ba5a5a47} + WLib.UserCtrls.Dev + + + {854a8e9d-31d4-4bc5-89ff-703318999065} + WLib.UserCtrls + + + {69d92973-b7bc-4c0b-8038-f9063d2eb2a1} + WLib + + + + + + + + + \ No newline at end of file diff --git a/YYGDZL.Business.DBPJ/App.config b/YYGDZL.Business.DBPJ/App.config new file mode 100644 index 0000000000000000000000000000000000000000..8e15646352ec1d9a84bbc6504ef6b46e16bf7823 --- /dev/null +++ b/YYGDZL.Business.DBPJ/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/YYGDZL.Business.DBPJ/MainForm.Designer.cs b/YYGDZL.Business.DBPJ/MainForm.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..63a73829a253fc47ee76363fd368b67030575092 --- /dev/null +++ b/YYGDZL.Business.DBPJ/MainForm.Designer.cs @@ -0,0 +1,264 @@ +namespace YYGDZL.App.DBPJ +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.ribbonControl1 = new DevExpress.XtraBars.Ribbon.RibbonControl(); + this.barBtnItemHGFX = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemFDYSFZ = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemGDZLPJ = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemYDFBT = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemZTTBZ = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemGZKJPZ = new DevExpress.XtraBars.BarButtonItem(); + this.barBtnItemCSPZ = new DevExpress.XtraBars.BarButtonItem(); + this.ribbonPagePJGC = new DevExpress.XtraBars.Ribbon.RibbonPage(); + this.ribbonPageGroupPJZB = new DevExpress.XtraBars.Ribbon.RibbonPageGroup(); + this.ribbonPageGroupGDPJ = new DevExpress.XtraBars.Ribbon.RibbonPageGroup(); + this.ribbonPageZTTBZ = new DevExpress.XtraBars.Ribbon.RibbonPage(); + this.ribbonPageGroupZTTBZ = new DevExpress.XtraBars.Ribbon.RibbonPageGroup(); + this.ribbonPagePZ = new DevExpress.XtraBars.Ribbon.RibbonPage(); + this.ribbonPageGroup1 = new DevExpress.XtraBars.Ribbon.RibbonPageGroup(); + this.dockFormControl1 = new WLib.UserCtrls.Dev.ChildForm.DockFormControl(); + this.defaultLookAndFeel1 = new DevExpress.LookAndFeel.DefaultLookAndFeel(this.components); + this.imageCollection1 = new DevExpress.Utils.ImageCollection(this.components); + ((System.ComponentModel.ISupportInitialize)(this.ribbonControl1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.imageCollection1)).BeginInit(); + this.SuspendLayout(); + // + // ribbonControl1 + // + this.ribbonControl1.ApplicationButtonText = null; + // + // + // + this.ribbonControl1.ExpandCollapseItem.Id = 0; + this.ribbonControl1.ExpandCollapseItem.Name = ""; + this.ribbonControl1.ExpandCollapseItem.RibbonStyle = DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText; + this.ribbonControl1.Images = this.imageCollection1; + this.ribbonControl1.Items.AddRange(new DevExpress.XtraBars.BarItem[] { + this.ribbonControl1.ExpandCollapseItem, + this.barBtnItemHGFX, + this.barBtnItemFDYSFZ, + this.barBtnItemGDZLPJ, + this.barBtnItemYDFBT, + this.barBtnItemZTTBZ, + this.barBtnItemGZKJPZ, + this.barBtnItemCSPZ}); + this.ribbonControl1.Location = new System.Drawing.Point(0, 0); + this.ribbonControl1.MaxItemId = 9; + this.ribbonControl1.Name = "ribbonControl1"; + this.ribbonControl1.Pages.AddRange(new DevExpress.XtraBars.Ribbon.RibbonPage[] { + this.ribbonPagePJGC, + this.ribbonPageZTTBZ, + this.ribbonPagePZ}); + this.ribbonControl1.SelectedPage = this.ribbonPagePJGC; + this.ribbonControl1.Size = new System.Drawing.Size(948, 147); + // + // barBtnItemHGFX + // + this.barBtnItemHGFX.Caption = "利用等标准粮回归分析"; + this.barBtnItemHGFX.Id = 1; + this.barBtnItemHGFX.ImageIndex = 7; + this.barBtnItemHGFX.Name = "barBtnItemHGFX"; + this.barBtnItemHGFX.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + this.barBtnItemHGFX.Tag = "ParamsConfigForm"; + // + // barBtnItemFDYSFZ + // + this.barBtnItemFDYSFZ.Caption = "分等因素赋值"; + this.barBtnItemFDYSFZ.Id = 2; + this.barBtnItemFDYSFZ.ImageIndex = 9; + this.barBtnItemFDYSFZ.Name = "barBtnItemFDYSFZ"; + this.barBtnItemFDYSFZ.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // barBtnItemGDZLPJ + // + this.barBtnItemGDZLPJ.Caption = "耕地质量评价"; + this.barBtnItemGDZLPJ.Id = 3; + this.barBtnItemGDZLPJ.ImageIndex = 4; + this.barBtnItemGDZLPJ.Name = "barBtnItemGDZLPJ"; + this.barBtnItemGDZLPJ.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // barBtnItemYDFBT + // + this.barBtnItemYDFBT.Caption = "样点分布图"; + this.barBtnItemYDFBT.Id = 4; + this.barBtnItemYDFBT.ImageIndex = 8; + this.barBtnItemYDFBT.Name = "barBtnItemYDFBT"; + this.barBtnItemYDFBT.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // barBtnItemZTTBZ + // + this.barBtnItemZTTBZ.Caption = "专题图编制"; + this.barBtnItemZTTBZ.Id = 5; + this.barBtnItemZTTBZ.ImageIndex = 6; + this.barBtnItemZTTBZ.Name = "barBtnItemZTTBZ"; + this.barBtnItemZTTBZ.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // barBtnItemGZKJPZ + // + this.barBtnItemGZKJPZ.Caption = "工作空间配置"; + this.barBtnItemGZKJPZ.Id = 6; + this.barBtnItemGZKJPZ.ImageIndex = 2; + this.barBtnItemGZKJPZ.Name = "barBtnItemGZKJPZ"; + this.barBtnItemGZKJPZ.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // barBtnItemCSPZ + // + this.barBtnItemCSPZ.Caption = "参数配置"; + this.barBtnItemCSPZ.Id = 7; + this.barBtnItemCSPZ.ImageIndex = 5; + this.barBtnItemCSPZ.Name = "barBtnItemCSPZ"; + this.barBtnItemCSPZ.RibbonStyle = ((DevExpress.XtraBars.Ribbon.RibbonItemStyles)(((DevExpress.XtraBars.Ribbon.RibbonItemStyles.Large | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithText) + | DevExpress.XtraBars.Ribbon.RibbonItemStyles.SmallWithoutText))); + // + // ribbonPagePJGC + // + this.ribbonPagePJGC.Groups.AddRange(new DevExpress.XtraBars.Ribbon.RibbonPageGroup[] { + this.ribbonPageGroupPJZB, + this.ribbonPageGroupGDPJ}); + this.ribbonPagePJGC.Name = "ribbonPagePJGC"; + this.ribbonPagePJGC.Text = "评价过程"; + // + // ribbonPageGroupPJZB + // + this.ribbonPageGroupPJZB.ItemLinks.Add(this.barBtnItemFDYSFZ); + this.ribbonPageGroupPJZB.ItemLinks.Add(this.barBtnItemHGFX); + this.ribbonPageGroupPJZB.Name = "ribbonPageGroupPJZB"; + this.ribbonPageGroupPJZB.Text = "评价准备"; + // + // ribbonPageGroupGDPJ + // + this.ribbonPageGroupGDPJ.ItemLinks.Add(this.barBtnItemGDZLPJ); + this.ribbonPageGroupGDPJ.Name = "ribbonPageGroupGDPJ"; + this.ribbonPageGroupGDPJ.Text = "耕地评价"; + // + // ribbonPageZTTBZ + // + this.ribbonPageZTTBZ.Groups.AddRange(new DevExpress.XtraBars.Ribbon.RibbonPageGroup[] { + this.ribbonPageGroupZTTBZ}); + this.ribbonPageZTTBZ.Name = "ribbonPageZTTBZ"; + this.ribbonPageZTTBZ.Text = "专题图编制"; + // + // ribbonPageGroupZTTBZ + // + this.ribbonPageGroupZTTBZ.ItemLinks.Add(this.barBtnItemYDFBT); + this.ribbonPageGroupZTTBZ.ItemLinks.Add(this.barBtnItemZTTBZ); + this.ribbonPageGroupZTTBZ.Name = "ribbonPageGroupZTTBZ"; + this.ribbonPageGroupZTTBZ.Text = "专题图编制"; + // + // ribbonPagePZ + // + this.ribbonPagePZ.Groups.AddRange(new DevExpress.XtraBars.Ribbon.RibbonPageGroup[] { + this.ribbonPageGroup1}); + this.ribbonPagePZ.Name = "ribbonPagePZ"; + this.ribbonPagePZ.Text = "数据配置"; + // + // ribbonPageGroup1 + // + this.ribbonPageGroup1.ItemLinks.Add(this.barBtnItemGZKJPZ); + this.ribbonPageGroup1.ItemLinks.Add(this.barBtnItemCSPZ); + this.ribbonPageGroup1.Name = "ribbonPageGroup1"; + this.ribbonPageGroup1.Text = "数据配置"; + // + // dockFormControl1 + // + this.dockFormControl1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.dockFormControl1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.dockFormControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dockFormControl1.DockingStyle = DevExpress.XtraBars.Docking.DockingStyle.Left; + this.dockFormControl1.FloatLocation = new System.Drawing.Point(100, 100); + this.dockFormControl1.IsCloseDockPanel = true; + this.dockFormControl1.Location = new System.Drawing.Point(0, 147); + this.dockFormControl1.Name = "dockFormControl1"; + this.dockFormControl1.Size = new System.Drawing.Size(948, 439); + this.dockFormControl1.TabIndex = 1; + // + // imageCollection1 + // + this.imageCollection1.ImageSize = new System.Drawing.Size(32, 32); + this.imageCollection1.ImageStream = ((DevExpress.Utils.ImageCollectionStreamer)(resources.GetObject("imageCollection1.ImageStream"))); + this.imageCollection1.Images.SetKeyName(0, "app.png"); + this.imageCollection1.Images.SetKeyName(1, "dingwei.png"); + this.imageCollection1.Images.SetKeyName(2, "folder.png"); + this.imageCollection1.Images.SetKeyName(3, "folder_1.png"); + this.imageCollection1.Images.SetKeyName(4, "google-maps.png"); + this.imageCollection1.Images.SetKeyName(5, "icon_shezhi.png"); + this.imageCollection1.Images.SetKeyName(6, "location_1.png"); + this.imageCollection1.Images.SetKeyName(7, "oeeanalysis.png"); + this.imageCollection1.Images.SetKeyName(8, "pointmap.png"); + this.imageCollection1.Images.SetKeyName(9, "weibiaoti15.png"); + this.imageCollection1.Images.SetKeyName(10, "zhexiantu.png"); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 14F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(948, 586); + this.Controls.Add(this.dockFormControl1); + this.Controls.Add(this.ribbonControl1); + this.Name = "MainForm"; + this.Ribbon = this.ribbonControl1; + this.Text = "广西三调耕地质量等别评价软件"; + this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + ((System.ComponentModel.ISupportInitialize)(this.ribbonControl1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.imageCollection1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private DevExpress.XtraBars.Ribbon.RibbonControl ribbonControl1; + private DevExpress.XtraBars.Ribbon.RibbonPage ribbonPagePJGC; + private DevExpress.XtraBars.Ribbon.RibbonPageGroup ribbonPageGroupPJZB; + private WLib.UserCtrls.Dev.ChildForm.DockFormControl dockFormControl1; + private DevExpress.LookAndFeel.DefaultLookAndFeel defaultLookAndFeel1; + private DevExpress.XtraBars.BarButtonItem barBtnItemHGFX; + private DevExpress.XtraBars.BarButtonItem barBtnItemFDYSFZ; + private DevExpress.XtraBars.BarButtonItem barBtnItemGDZLPJ; + private DevExpress.XtraBars.BarButtonItem barBtnItemYDFBT; + private DevExpress.XtraBars.BarButtonItem barBtnItemZTTBZ; + private DevExpress.XtraBars.BarButtonItem barBtnItemGZKJPZ; + private DevExpress.XtraBars.BarButtonItem barBtnItemCSPZ; + private DevExpress.XtraBars.Ribbon.RibbonPageGroup ribbonPageGroupGDPJ; + private DevExpress.XtraBars.Ribbon.RibbonPage ribbonPageZTTBZ; + private DevExpress.XtraBars.Ribbon.RibbonPageGroup ribbonPageGroupZTTBZ; + private DevExpress.XtraBars.Ribbon.RibbonPage ribbonPagePZ; + private DevExpress.XtraBars.Ribbon.RibbonPageGroup ribbonPageGroup1; + private DevExpress.Utils.ImageCollection imageCollection1; + } +} \ No newline at end of file diff --git a/YYGDZL.Business.DBPJ/MainForm.cs b/YYGDZL.Business.DBPJ/MainForm.cs new file mode 100644 index 0000000000000000000000000000000000000000..4dbe5e38f72933ccd514e72789c8cf68cda4cfaf --- /dev/null +++ b/YYGDZL.Business.DBPJ/MainForm.cs @@ -0,0 +1,56 @@ +using DevExpress.XtraBars; +using DevExpress.XtraBars.Ribbon; +using DevExpress.XtraBars.Ribbon.Internal; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using WLib.UserCtrls.Dev.SkinCtrl; +using YYGDZL.Business.WinForm.Config.ParamsConfig; + +namespace YYGDZL.App.DBPJ +{ + + /// + /// + /// + public partial class MainForm : RibbonForm + { + private List _subForms = new List(); + + + public MainForm() + { + InitializeComponent(); + + var form = new ParamsConfigForm(); + this.defaultLookAndFeel1.SetSkin(SkinOpt.Office2010蓝色); + foreach(RibbonPage page in this.ribbonControl1.DefaultPageCategory.Pages) + { + foreach (RibbonPageGroup pageGroup in page.Groups) + { + foreach (BarItemLink itemLink in pageGroup.ItemLinks) + { + itemLink.Item.ItemClick += (sender, e) => + { + this.dockFormControl1.AddFormToFloatPanel(new ParamsConfigForm(), new Point(10,10), 1000, 600); + //this.dockFormControl1.AddFormToFloatPanel(form, new Point(10,10), 1000, 600); + }; + } + } + } + //this.barBtnItemCSPZ.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemFDYSFZ.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemGDZLPJ.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemGZKJPZ.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemHGFX.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemYDFBT.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + //this.barBtnItemZTTBZ.ItemClick += (sender, e) => this.dockFormControl1.AddFormToTabPanel(new ParamsConfigForm()); + } + } +} diff --git a/YYGDZL.Business.DBPJ/MainForm.resx b/YYGDZL.Business.DBPJ/MainForm.resx new file mode 100644 index 0000000000000000000000000000000000000000..40148fdfef2215b8bef138c796e6cbe55cf0fa24 --- /dev/null +++ b/YYGDZL.Business.DBPJ/MainForm.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 194, 17 + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFpEZXZFeHByZXNzLlV0aWxzLnYxMC4xLCBWZXJzaW9uPTEwLjEu + Ni4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI4OGQxNzU0ZDcwMGU0OWEMAwAAAFFT + eXN0ZW0uRHJhd2luZywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRv + a2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAAChEZXZFeHByZXNzLlV0aWxzLkltYWdlQ29sbGVjdGlvblN0 + cmVhbWVyAgAAAAlJbWFnZVNpemUERGF0YQQHE1N5c3RlbS5EcmF3aW5nLlNpemUDAAAAAgIAAAAF/P// + /xNTeXN0ZW0uRHJhd2luZy5TaXplAgAAAAV3aWR0aAZoZWlnaHQAAAgIAwAAACAAAAAgAAAACQUAAAAP + BQAAANoTAAACjgEAAIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAAFzUkdCAK7OHOkA + AAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABI0lEQVRYR+2VQQrCMBBFK3gKT+Jx + UvEOLgVx6zXcScYT6NqNa4/gTnAnijqTibRpkk5TRIQ8GEiaTP6LFFtkvkoJTx79AAy3Jaba1FZKz7ir + Ca3D7TPGEiHdSIfqFc/qULi+8MwglugrQOF8cxeRRB8BCtf+cEurRKoAhQdu7hKVSBEwB95pLCUokSKA + 4FzBnmdxFDzCOakC893QPIMDP/Gj9DWekSpgMRJnntWJ3tzSVwAxa/VfQhSOULO0AgIIrtt3QhyOKL18 + N6xFVTwH3NXEvhOdwr/BT8OTQGNpTbaKu7r1TTdj7vKAGyYwai06zPkj8u1zC/cpWHCXB9wgwScgIQtk + gSzwHwLicgSkFRd4f8dLOImq+jUs4dhYD1WmRlG8AO/X7Ax2hOodAAAAAElFTkSuQmCCUgIAAIlQTkcN + ChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAA + CXBIWXMAAA7DAAAOwwHHb6hkAAAB50lEQVRYR9WWy0rDQBSG40bQna5cCYKKC+/6AvoMdamdSUvBpU/g + Y6iPUMHOpK14AXfiQhS76gN00aXWy6YI1knyT5ImTTJNY4sfHDL858w/JzSdGe3/Q/gDRiOA8hcRHY1e + rEEZIjrf1qjxZjVA2JWWrc4g84dQ/m6/cWxcYkZKUH7jW8CMuvj9m/aYnYhnw5OTkYHDAHQbPkO1kd9A + lq1DsaG8jXo7EhNnQvmTlcsZ81Bc9s7HY+dHMtBkD67PDxQFKLtNZXGJ04Tq3zXNxU1yxWl1T8o/UfwI + JR1kA9nyEpQQlDtNgJL3SBuwtlerqA4lgs6YYxhp6iGylrCaiBaKmmKXq4nnE7JBpJk3wjC9rGDfVl3A + 2zxS/WYywlCtzTofdTD0yjKqBDrfEOI1kqdanm1ppLKAbBC/GWUfyAQh5U3bj71ataHe5pFqGzagRCMX + J0b44l5kfSRKRQkZaQN5Y06xgRJDYRtKOvT1YrL4WBypaaBXd/ptINPfhBikV+FsEooCclJcE5R9YdQb + 1+cOSh94mzisTkF1ibqWE2PXmUt4C2oCzJuMNDLDPAMkve6EhfJsVz1l98gMwIGx2m2KkHs7xQ7nj6Pi + BBxSYr+02HMhfwyNsGv50Ii6lg8NwlYwSoCm/QLQb9VQP9WHmwAAAABJRU5ErkJggvQAAACJUE5HDQoa + CgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAABc1JHQgCuzhzpAAAABGdBTUEAALGPC/xhBQAAAAlw + SFlzAAAOwwAADsMBx2+oZAAAAIlJREFUWEfF18ENQEAQRmGl2h7UowVTgFqUgpWI0zvYWXn+5Ls8MXE1 + PCvLVu3vxXq/+cFKHGndG5cZD7foGh3MSI+OZaVGh3o0j45Yxtj//YALRhNGE0YTRhNGE0YTRhNGE0YT + RhNGE0YTRhNGE0YTRhNGE0YTRhNGE0YTRlP9N5zwgeXfn5M4Tme7g01zi4UgAAAAAElFTkSuQmCCTgEA + AIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L + /GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA40lEQVRYR+2QMQrCMBRA/yXcvISzp3DQtYl6BufuLt5C + EDSJg7On8AqCe8VN839qJyFt8mOh5MHDfGo/r4EMSP3uJDtu8RbkZeTXnPkjKECV9eTH/X9TTwx0DUBY + byEkYKkn7j0G3aFjACL003qvpzCEeoQHIPQFEUQHIDERPAFq73YE6g4RATGw3EAMOSAH5IBhBcjTwu1q + KZImwJR+UwYgKzMnCzOlWeoZ/X5JHkA7rULfaBb6BevjmM5I8gAfyQNo5y/VrnmODPMG2oqwBoTQBAh9 + hcJG/FuhKhtgDr3aLwAfqs3qVE2S1z4AAAAASUVORK5CYILhAQAAiVBORw0KGgoAAAANSUhEUgAAACAA + AAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdv + qGQAAAF2SURBVFhH7ZQ9TsRADEbDdnT0IE5ATUFPQYlEC0wW7kCZQ9DTIgp2NsAFkBAnoOAI0CEh0aEQ + T+Lg8XhiU2yD8qQnJf4+Z7N/KSb+H+XypbUZdPVDn6wYV+9HL8wtmrW+uQJObjbFF+Wab6Ksv8ULJPrr + rp/Mn/pP5CPJVE79RrKUE4lm7F06vxfnGrSsCTj/mMw4Wh5By5pd/z2ZcbQ8gpY1gbPFbjKLaL+S0ZxD + y5oIn8/vd8J87i+STIUv5KRIeU4VaYmL4PHx3XbSkTQhLVIRPqPnov4t9FTE5V7EOqeakZZBRMpAwPkr + MauaWchNSBdApAx1/lXsuPZxjHMT/AJIVc2SjHt+uxW6dNadXw7HKnwZwOOGPVQkgaPn9eEYoJkKL+N5 + biZJyc2z0CJdRgH96/gKPT7/E3yZCsAvW8p+/UxmZviiJGD5TVBNSIs5ESmTNCEtjglY/qKgiXJ5mCxq + ApabMCMtawKjN7E4CJ2JiYii+AG4upbyPcEagQAAAABJRU5ErkJggrQCAACJUE5HDQoaCgAAAA1JSERS + AAAAIAAAACAIBgAAAHN6evQAAAABc1JHQgCuzhzpAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwwAA + DsMBx2+oZAAAAklJREFUWEftl72KFEEQx+c8/Eg8X8AHuEgMREVOMDE0kkud7tljA3H1AQwOLvIVfAQX + 3a5Z5FBO0cREDi40EjHzUDQV5G6dnqnqr6me7VlWTfxBQff/X1XdO7M9s5v9Z2EkzFqxOV5F9w8j1DV2 + A1pfOlK9x5GFFsyfrtfzO5PLjaY+1HOX/M0ZHPVmxSykg+A0P/cQtepKwRGTm8LMXzwWLpwfRjK26Ecw + r+5zea/WYkh46eU3mj+fC5cs1C0cpTEcn8NRA/UcPj6JSgdF+YndxKJQL6m+oJKAKSqHqLQpYKe6MnuZ + mDxApY2oFqVevegqIi8MAb8ww4f8ZExDdRcVC3m1D6q+p1IdenoI6YUaocIwKG9WSV9Nclcjqb6j4iPh + GHO+odIwc461qGoH5QV0HExzJ0Jiusu8Wjc8WNFBf+G0ry97F9RnuM8ftwIu8WuxooNUr2t/c3wKFR7q + I9R1VNqwa5Eo4UW29ew8qhYJ92tfH6su2ObIaPd0lqsbfA6JboTEdJd5tW4wrGTF9HY0QVbnvPGOUfGh + OgFvUWkY7a4ZT8KrLH9+EZ0IUn3G5HeoWGyjeIRY7woqCcSaafQ5t01thJ+cIL8XVNT19tJHTU42cMYj + pk/6b4AKuFuwCNRPlHuodGAX93cs4CeO0pDwEEcNXM8o7ia2Zye8uQ79bOco4GwrVxPOk3CLYuHC+WH0 + hgoHzs8x/UoNG+bTdaNtlVdRXfCTp2Ca4q0Q6mM95/6YSHiEoyUi4cBswo2/hn6i/dMNLI0s+w3gf2rd + /a8wfwAAAABJRU5ErkJggtgBAACJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAABc1JH + QgCuzhzpAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAW1JREFUWEftlLFKxEAQ + hlexEgtBDkvRzpfwSRQ3yaFg4xPI2foA19sKR3ZjYXsvICj4BGJvY6XixWwyk9ud3WSzuSvzwQ878/+Z + zJHl2MDAQCtc3sHJz7nYZdHsCKo1EKcTFsm8kniHrps6B0qyfXBWIBK31mAuF+BWUJ/q4nEbkj0wF7jX + zs2K5Un1LOlfzXfKfhDLBZ6ho+6E9lmILPKNdt+HawGEyxdjOJdf4NhgJpi2BRQ42PcCn98IXWCcHTKe + XpdnBQ5GTeZb4JigHwxdAAe1icuf4rmbMo+gF0zTJ8CBPiG07sxyAfXLitufnYJjw8VlnUUhtO6MvoCu + s3QPEiY0h9C6M00LKLloytC6MyELlJfPyn0X/be6DqZtASUdl08VjG+B6GlU5hIxdvtEvXANMjV19Nzq + TZIeOwfq4vIT0uo+/DkzKxM9jOzB4hdcm0gsjOzaiGcHxR/PK1R+uPxgeb4J1cDAAIGxf8qg/u4IYi3C + AAAAAElFTkSuQmCC1wEAAIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAAFzUkdCAK7O + HOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABbElEQVRYR+2TsUoEMRCGIyJ2 + gp0KYmmpqO9gYyNooRaXrKfdPYKWNj6BWFnYiGcmIOcz2PgeomitKGeymWSz2WzWU4+zyAfDTuafzPzc + 7pFEIpGwMC4I5Q94GgExA9n1Aun0JvE0JOoMMOjboIJjtUyLr2L2C75jQIWPrn/oZ3cJq2XaYgezCD8x + wIDLOMWT7vVx71K+jtUAsW+AwWN4+M2xrTOxEe7B5SZqiRlQ0O6KHdDpTdmBFN5tzvhRrhsYbBVaHueo + BGgyoGDwbIcdnk1gtUBrr6R1O2P7FBReZL6Z57U0/gLwWQztj2G1irt4IHwDZpAJKi5Q0ZrL9tW47VMw + flLpacQ10ObLcsBTniv8YUzs24UmMphDVXNwN9vYU8I1kMGivPCW5wp12cUfXAfl8yU91ht9BT6uFtIN + u5fTJT3WWzHQhFlO4R4rYUyfij35961lUAN/TjLwLwy4H8ywI4M13JxIjBJCvgCHVl0tzz1xEgAAAABJ + RU5ErkJgglMBAACJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAABc1JHQgCuzhzpAAAA + BGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAOhJREFUWEftzrEKwjAQBuAsTm4iddVd + 8H2kILFSnPUFfA1X95jWRbs6+zI+gdaa6gUaklxarZIPDtq7/McRz2uf+X5FaHLJayc6H0aT26u+4q8O + QO1ChRRQu1AhBdQukxBNrto3pfjQF1+GzA7Qv0EzWb7Ous83fCM6NTI5oFE/fwDlLM+exR+C6oDoMC16 + i3QkOrJ425Gyj4rSgZhagAtKIRtX9iE4h2WtKkz5Ueovs0BM3uAcljVVWNUvwTksay5hmMXkC64LQhaQ + GR+KPwTXA5y16oAmSqsqVGdpRaceoWzSWHmehJA7FMx5Y2Sb388AAAAASUVORK5CYILFAgAAiVBORw0K + GgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJ + cEhZcwAADsMAAA7DAcdvqGQAAAJaSURBVFhH1Za/axRBFMdHIaAYEFsrUcRCBBHESkE7xT9AMGRnBjzQ + QrRQ7E5L/wBBBYlgZTQ7b6MQrFIoVnZCxEYwYisYQTGi5+zum7mZ2fm13jV+4AM3733fG3JsliP/F6w8 + SxiMktJqg5DRFpyaAhTeey/KcSJYedq7tLfiHW7sAYXf/mUTmI1veFomcQc4nO/UJjUIgxtG8E9bK+fI + 4M3MeFA+3RTWCF0+amRtTXx9Wj7BroMZcmFipe2Jh1bOpwmDy9G+hopf0QCD19JveKrzGzrvWvdMmPiK + 9U2seFDDKRgc09mYJlQckrUfePJQwB0cfIUVPxQ+6QtiUnEGJ1oY/MRPY1h1Hz9J1CCHA1jpwuCtzsXs + fP2y5tLmjuNJooflJSZzL3aQkXzqWXVFZ1IqWPVAnrt/eY3KXny+yy7UcrjW1DictOr9XW/2+GDiS5PR + uMP1/3oh9pD5cl+nl+MF+Y6IwWG3zjaYw6axXsgcOLxssvTZ/rbgLnF1ycmkYPB9POcuc3UZLO6M9lOo + tyOHc6pgX+gaoljdhpkFrORBxUd7tzqEpMtH2qAHnRM3sZKGis/NjIaKD+NFAalYxLSNlRPXsRqmEIcx + v4YVxFoU0OTq4+3eTOqbULn56iBWEGtJQBNfX8nhNqZsmreq7F9ancWKg7vIVTEcbfX2LQPfRNu/iycH + Do/sJY41w2HG5cqMZ6KDdxHK4UQy40rFrWamF75FylTfZ+iZiEJh07vsXw0+eDHY073eZX2dGKZ/svVz + cG8GN0yJYqF+9693LjKlS6cwnQEhfwH/NWG2CCft9wAAAABJRU5ErkJggjABAACJUE5HDQoaCgAAAA1J + SERSAAAAIAAAACAIBgAAAHN6evQAAAABc1JHQgCuzhzpAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAMVJREFUWEftkjsOwjAQRI1EhZC4A9TcCRdJwSW4TwocIXqOwjGCaIIi1snYMh+v + E3CxTxrJO56Mt4gSBCFvdN32+guyAHeBLl+YLU0JcBbAb4rjhlwmWPYNmLc6XOZ0ywCLPoFZXTfufF5T + KhIseQfmbLY8LRyP9U9ggc/TvzoZP1dWq97fV0tyI3hVjD4qSDujA4NQOXqDGrodGXzEn7W5OXeTgA9a + uvPO3GmamNACP0UWyGqBMRRNqCRF0YRKUiQI+aHUA7HqWINP/+60AAAAAElFTkSuQmCCCw== + + + + 17, 17 + + + 44 + + \ No newline at end of file diff --git a/YYGDZL.Business.DBPJ/Program.cs b/YYGDZL.Business.DBPJ/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..50bf193ecc7a274c0b730b7c6de30b8b6e5d900d --- /dev/null +++ b/YYGDZL.Business.DBPJ/Program.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows.Forms; + +namespace YYGDZL.App.DBPJ +{ + static class Program + { + /// + /// 应用程序的主入口点。 + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + //var dbHelper = new DbHelper(ConnStringHelper.Sqlite(@"F:\YYCode\耕地质量综合管理平台重构\App\Config\config.db"), EDbProviderType.SqLite); + //var fdysList = dbHelper.Query<区域_分等因素>(@"select * from 区域_分等因素"); + //var fdysList2 = dbHelper.SimpleQuery<区域_分等因素>(); + + //Console.WriteLine("编号\t分等因素代码\t分等因素名称\t字段类型\t字段长度\t值域下限\t值域上限\t是否可选"); + //foreach (var fdys in fdysList) + //{ + // Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}", fdys.编号, fdys.分等因素代码, fdys.分等因素名称, fdys.字段类型, fdys.字段长度, fdys.值域下限, fdys.值域上限, fdys.是否可选); + //} + //Console.WriteLine(nameof(区域_分等因素.分等因素代码)); + //Console.ReadKey(); + } + } +} diff --git a/YYGDZL.Business.DBPJ/Properties/AssemblyInfo.cs b/YYGDZL.Business.DBPJ/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..ba3a00bb87210b26a12bfeaa1b7099982720576d --- /dev/null +++ b/YYGDZL.Business.DBPJ/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("耕地质量等别评价软件")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("华南自然资源科学技术研究院")] +[assembly: AssemblyProduct("耕地质量等别评价软件")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("0d21a25a-06c7-48dc-a513-1a5e9da99b5c")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/YYGDZL.Business.DBPJ/Properties/Resources.Designer.cs b/YYGDZL.Business.DBPJ/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..e1deb1c2e636d77c74a52bdfe3acf2fc0025d5e1 --- /dev/null +++ b/YYGDZL.Business.DBPJ/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace YYGDZL.App.DBPJ.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("YYGDZL.App.DBPJ.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/YYGDZL.Business.DBPJ/Properties/Resources.resx b/YYGDZL.Business.DBPJ/Properties/Resources.resx new file mode 100644 index 0000000000000000000000000000000000000000..af7dbebbacef595e3089c01c05671016c21a8304 --- /dev/null +++ b/YYGDZL.Business.DBPJ/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/YYGDZL.Business.DBPJ/Properties/Settings.Designer.cs b/YYGDZL.Business.DBPJ/Properties/Settings.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..8d224c686be523996381844dcc9a77d00238223f --- /dev/null +++ b/YYGDZL.Business.DBPJ/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace YYGDZL.App.DBPJ.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/YYGDZL.Business.DBPJ/Properties/Settings.settings b/YYGDZL.Business.DBPJ/Properties/Settings.settings new file mode 100644 index 0000000000000000000000000000000000000000..39645652af62950ebf3b28ec3a5400dcec30b1c4 --- /dev/null +++ b/YYGDZL.Business.DBPJ/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/YYGDZL.Business.DBPJ/YYGDZL.App.DBPJ.csproj b/YYGDZL.Business.DBPJ/YYGDZL.App.DBPJ.csproj new file mode 100644 index 0000000000000000000000000000000000000000..4e92cd164b809ca4603a78c32b907c9556ec6a90 --- /dev/null +++ b/YYGDZL.Business.DBPJ/YYGDZL.App.DBPJ.csproj @@ -0,0 +1,137 @@ + + + + + Debug + AnyCPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C} + WinExe + YYGDZL.App.DBPJ + 耕地质量等别评价软件 + v4.5 + 512 + true + + + AnyCPU + true + full + false + ..\Apps\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + MainForm.cs + + + + + MainForm.cs + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {b69ea382-d83f-481f-88b9-f0ac9dbce928} + Dapper + + + {0e418c82-3534-4604-bee4-4e5bd0bb926d} + WLib.ArcGis + + + {32fb07dd-7400-4011-bebc-72ed503b4911} + WLib.Envir + + + {aeb9c1be-96cf-42f5-ab08-7a45817cf906} + WLib.Files.Excel + + + {140b142f-0a73-4561-91ca-2ad98cf38572} + WLib.Files.Word + + + {5ac4fbca-7dc4-49ad-b3ef-f5a7ba5a5a47} + WLib.UserCtrls.Dev + + + {854a8e9d-31d4-4bc5-89ff-703318999065} + WLib.UserCtrls + + + {69d92973-b7bc-4c0b-8038-f9063d2eb2a1} + WLib + + + {0c939673-21a7-468f-8f83-aa1227cbc558} + YYGDZL.Business.Base + + + {7591876a-b933-4372-a09b-780a30abb40e} + YYGDZL.Business.WinForm + + + + + + + \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.Designer.cs b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..17636f47b08fdc1e29428068283e8e123a668069 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.Designer.cs @@ -0,0 +1,154 @@ +namespace YYGDZL.Business.WinForm.Config.ParamsConfig +{ + partial class EvaluationIndexForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.listBoxCtrlParamsType = new DevExpress.XtraEditors.ListBoxControl(); + this.gridControl1 = new DevExpress.XtraGrid.GridControl(); + this.gridView1 = new DevExpress.XtraGrid.Views.Grid.GridView(); + this.sBtnSave = new DevExpress.XtraEditors.SimpleButton(); + this.sBtnDelete = new DevExpress.XtraEditors.SimpleButton(); + this.sBtnReset = new DevExpress.XtraEditors.SimpleButton(); + this.sBtnAdd = new DevExpress.XtraEditors.SimpleButton(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + ((System.ComponentModel.ISupportInitialize)(this.listBoxCtrlParamsType)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridControl1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridView1)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // listBoxCtrlParamsType + // + this.listBoxCtrlParamsType.Dock = System.Windows.Forms.DockStyle.Left; + this.listBoxCtrlParamsType.Location = new System.Drawing.Point(0, 0); + this.listBoxCtrlParamsType.Name = "listBoxCtrlParamsType"; + this.listBoxCtrlParamsType.Size = new System.Drawing.Size(120, 450); + this.listBoxCtrlParamsType.TabIndex = 0; + // + // gridControl1 + // + this.gridControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.gridControl1.Location = new System.Drawing.Point(120, 0); + this.gridControl1.MainView = this.gridView1; + this.gridControl1.Name = "gridControl1"; + this.gridControl1.Size = new System.Drawing.Size(570, 409); + this.gridControl1.TabIndex = 1; + this.gridControl1.ViewCollection.AddRange(new DevExpress.XtraGrid.Views.Base.BaseView[] { + this.gridView1}); + // + // gridView1 + // + this.gridView1.GridControl = this.gridControl1; + this.gridView1.Name = "gridView1"; + // + // sBtnSave + // + this.sBtnSave.Dock = System.Windows.Forms.DockStyle.Fill; + this.sBtnSave.Location = new System.Drawing.Point(473, 3); + this.sBtnSave.Name = "sBtnSave"; + this.sBtnSave.Size = new System.Drawing.Size(94, 35); + this.sBtnSave.TabIndex = 0; + this.sBtnSave.Text = "保存(&S)"; + // + // sBtnDelete + // + this.sBtnDelete.Dock = System.Windows.Forms.DockStyle.Fill; + this.sBtnDelete.Location = new System.Drawing.Point(373, 3); + this.sBtnDelete.Name = "sBtnDelete"; + this.sBtnDelete.Size = new System.Drawing.Size(94, 35); + this.sBtnDelete.TabIndex = 0; + this.sBtnDelete.Text = "删除(&D)"; + // + // sBtnReset + // + this.sBtnReset.Dock = System.Windows.Forms.DockStyle.Fill; + this.sBtnReset.Location = new System.Drawing.Point(3, 3); + this.sBtnReset.Name = "sBtnReset"; + this.sBtnReset.Size = new System.Drawing.Size(94, 35); + this.sBtnReset.TabIndex = 0; + this.sBtnReset.Text = "重置(&R)"; + // + // sBtnAdd + // + this.sBtnAdd.Dock = System.Windows.Forms.DockStyle.Fill; + this.sBtnAdd.Location = new System.Drawing.Point(273, 3); + this.sBtnAdd.Name = "sBtnAdd"; + this.sBtnAdd.Size = new System.Drawing.Size(94, 35); + this.sBtnAdd.TabIndex = 0; + this.sBtnAdd.Text = "添加(&A)"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 5; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); + this.tableLayoutPanel1.Controls.Add(this.sBtnSave, 4, 0); + this.tableLayoutPanel1.Controls.Add(this.sBtnReset, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.sBtnDelete, 3, 0); + this.tableLayoutPanel1.Controls.Add(this.sBtnAdd, 2, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.tableLayoutPanel1.Location = new System.Drawing.Point(120, 409); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(570, 41); + this.tableLayoutPanel1.TabIndex = 3; + // + // EvaluationIndexForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 14F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(690, 450); + this.Controls.Add(this.gridControl1); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.listBoxCtrlParamsType); + this.Name = "EvaluationIndexForm"; + this.Text = "评价指数配置"; + ((System.ComponentModel.ISupportInitialize)(this.listBoxCtrlParamsType)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridControl1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridView1)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private DevExpress.XtraEditors.ListBoxControl listBoxCtrlParamsType; + private DevExpress.XtraGrid.GridControl gridControl1; + private DevExpress.XtraGrid.Views.Grid.GridView gridView1; + private DevExpress.XtraEditors.SimpleButton sBtnSave; + private DevExpress.XtraEditors.SimpleButton sBtnDelete; + private DevExpress.XtraEditors.SimpleButton sBtnAdd; + private DevExpress.XtraEditors.SimpleButton sBtnReset; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + } +} \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.cs b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.cs new file mode 100644 index 0000000000000000000000000000000000000000..da97efa8c2b2ad0fc9cda7d33e695ea5e6da3356 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.cs @@ -0,0 +1,21 @@ +using DevExpress.XtraEditors; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace YYGDZL.Business.WinForm.Config.ParamsConfig +{ + public partial class EvaluationIndexForm : XtraForm + { + public EvaluationIndexForm() + { + InitializeComponent(); + } + } +} diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.resx b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.resx new file mode 100644 index 0000000000000000000000000000000000000000..1af7de150c99c12dd67a509fe57c10d63e4eeb04 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/EvaluationIndexForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.Designer.cs b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..7ce1f7154ebc5d10da4e12c1d78207884b3f8b1c --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.Designer.cs @@ -0,0 +1,165 @@ +namespace YYGDZL.Business.WinForm.Config.ParamsConfig +{ + partial class ParamsConfigForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ParamsConfigForm)); + this.imageCollection1 = new DevExpress.Utils.ImageCollection(this.components); + this.navBarCtrlPlan = new DevExpress.XtraNavBar.NavBarControl(); + this.navBarCtrlParams = new DevExpress.XtraNavBar.NavBarControl(); + this.panelCtrl = new DevExpress.XtraEditors.PanelControl(); + this.panelCtrlContent = new DevExpress.XtraEditors.PanelControl(); + this.panelCtrlTips = new DevExpress.XtraEditors.PanelControl(); + this.labelControlTips = new DevExpress.XtraEditors.LabelControl(); + ((System.ComponentModel.ISupportInitialize)(this.imageCollection1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.navBarCtrlPlan)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.navBarCtrlParams)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrl)).BeginInit(); + this.panelCtrl.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrlContent)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrlTips)).BeginInit(); + this.panelCtrlTips.SuspendLayout(); + this.SuspendLayout(); + // + // imageCollection1 + // + this.imageCollection1.ImageStream = ((DevExpress.Utils.ImageCollectionStreamer)(resources.GetObject("imageCollection1.ImageStream"))); + this.imageCollection1.Images.SetKeyName(0, "configPlan.png"); + this.imageCollection1.Images.SetKeyName(1, "selectedPlan.png"); + this.imageCollection1.Images.SetKeyName(2, "parameter.png"); + this.imageCollection1.Images.SetKeyName(3, "tixingshixin.png"); + // + // navBarCtrlPlan + // + this.navBarCtrlPlan.ActiveGroup = null; + this.navBarCtrlPlan.AllowSelectedLink = true; + this.navBarCtrlPlan.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.HotFlat; + this.navBarCtrlPlan.Dock = System.Windows.Forms.DockStyle.Left; + this.navBarCtrlPlan.LargeImages = this.imageCollection1; + this.navBarCtrlPlan.Location = new System.Drawing.Point(0, 0); + this.navBarCtrlPlan.Name = "navBarCtrlPlan"; + this.navBarCtrlPlan.OptionsNavPane.ExpandedWidth = 142; + this.navBarCtrlPlan.PaintStyleKind = DevExpress.XtraNavBar.NavBarViewKind.NavigationPane; + this.navBarCtrlPlan.Size = new System.Drawing.Size(127, 572); + this.navBarCtrlPlan.SmallImages = this.imageCollection1; + this.navBarCtrlPlan.TabIndex = 16; + this.navBarCtrlPlan.Text = "navBarControl1"; + this.navBarCtrlPlan.View = new DevExpress.XtraNavBar.ViewInfo.SkinNavigationPaneViewInfoRegistrator(); + this.navBarCtrlPlan.MouseDown += new System.Windows.Forms.MouseEventHandler(this.NavBarCtrl_MouseDown); + // + // navBarCtrlParams + // + this.navBarCtrlParams.ActiveGroup = null; + this.navBarCtrlParams.AllowSelectedLink = true; + this.navBarCtrlParams.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.HotFlat; + this.navBarCtrlParams.Dock = System.Windows.Forms.DockStyle.Left; + this.navBarCtrlParams.Location = new System.Drawing.Point(127, 0); + this.navBarCtrlParams.Name = "navBarCtrlParams"; + this.navBarCtrlParams.OptionsNavPane.ExpandedWidth = 142; + this.navBarCtrlParams.PaintStyleKind = DevExpress.XtraNavBar.NavBarViewKind.NavigationPane; + this.navBarCtrlParams.Size = new System.Drawing.Size(192, 572); + this.navBarCtrlParams.SmallImages = this.imageCollection1; + this.navBarCtrlParams.TabIndex = 15; + this.navBarCtrlParams.Text = "navBarControl1"; + this.navBarCtrlParams.View = new DevExpress.XtraNavBar.ViewInfo.SkinNavigationPaneViewInfoRegistrator(); + this.navBarCtrlParams.MouseDown += new System.Windows.Forms.MouseEventHandler(this.NavBarCtrl_MouseDown); + // + // panelCtrl + // + this.panelCtrl.Controls.Add(this.panelCtrlContent); + this.panelCtrl.Controls.Add(this.panelCtrlTips); + this.panelCtrl.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelCtrl.Location = new System.Drawing.Point(319, 0); + this.panelCtrl.Name = "panelCtrl"; + this.panelCtrl.Size = new System.Drawing.Size(694, 572); + this.panelCtrl.TabIndex = 17; + // + // panelCtrlContent + // + this.panelCtrlContent.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelCtrlContent.Location = new System.Drawing.Point(2, 49); + this.panelCtrlContent.Name = "panelCtrlContent"; + this.panelCtrlContent.Size = new System.Drawing.Size(690, 521); + this.panelCtrlContent.TabIndex = 0; + // + // panelCtrlTips + // + this.panelCtrlTips.Controls.Add(this.labelControlTips); + this.panelCtrlTips.Dock = System.Windows.Forms.DockStyle.Top; + this.panelCtrlTips.Location = new System.Drawing.Point(2, 2); + this.panelCtrlTips.Name = "panelCtrlTips"; + this.panelCtrlTips.Size = new System.Drawing.Size(690, 47); + this.panelCtrlTips.TabIndex = 0; + // + // labelControlTips + // + this.labelControlTips.Appearance.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.labelControlTips.Appearance.ImageIndex = 3; + this.labelControlTips.Appearance.ImageList = this.imageCollection1; + this.labelControlTips.AutoSizeMode = DevExpress.XtraEditors.LabelAutoSizeMode.Vertical; + this.labelControlTips.Dock = System.Windows.Forms.DockStyle.Top; + this.labelControlTips.ImageAlignToText = DevExpress.XtraEditors.ImageAlignToText.LeftCenter; + this.labelControlTips.Location = new System.Drawing.Point(2, 2); + this.labelControlTips.Name = "labelControlTips"; + this.labelControlTips.Size = new System.Drawing.Size(686, 20); + this.labelControlTips.TabIndex = 1; + // + // ParamsConfigForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 14F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1013, 572); + this.Controls.Add(this.panelCtrl); + this.Controls.Add(this.navBarCtrlParams); + this.Controls.Add(this.navBarCtrlPlan); + this.Name = "ParamsConfigForm"; + this.Text = "参数配置"; + this.Load += new System.EventHandler(this.ParamsConfigForm_Load); + ((System.ComponentModel.ISupportInitialize)(this.imageCollection1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.navBarCtrlPlan)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.navBarCtrlParams)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrl)).EndInit(); + this.panelCtrl.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrlContent)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.panelCtrlTips)).EndInit(); + this.panelCtrlTips.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + private DevExpress.Utils.ImageCollection imageCollection1; + private DevExpress.XtraNavBar.NavBarControl navBarCtrlParams; + private DevExpress.XtraNavBar.NavBarControl navBarCtrlPlan; + private DevExpress.XtraEditors.PanelControl panelCtrl; + private DevExpress.XtraEditors.PanelControl panelCtrlContent; + private DevExpress.XtraEditors.PanelControl panelCtrlTips; + private DevExpress.XtraEditors.LabelControl labelControlTips; + } +} \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.cs b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.cs new file mode 100644 index 0000000000000000000000000000000000000000..c7c9e820ac128a7846f8bd1e61aeffc5d996f941 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.cs @@ -0,0 +1,130 @@ +using DevExpress.XtraEditors; +using DevExpress.XtraNavBar; +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using WLib.WinForm; +using YYGDZL.Business.Base.Config; +using static YYGDZL.Business.Base.Config.Params.ParamsConfigsManager; + +namespace YYGDZL.Business.WinForm.Config.ParamsConfig +{ + /// + /// 评价参数配置窗口 + /// + public partial class ParamsConfigForm : XtraForm + { + /// + /// 评价参数配置窗口 + /// + public ParamsConfigForm() + { + InitializeComponent(); + } + /// + /// 设置参数方案/参数内容NavBarControl列表项的图标 + /// + /// + /// 当前选中项 + private void ResetImageOfItemsLinks(NavBarControl navBarCtrl, NavBarItemLink selectedLink) + { + foreach (NavBarGroup barGroup in navBarCtrl.Groups) + { + barGroup.SmallImageIndex = -1; + foreach (NavBarItemLink itemLink in barGroup.ItemLinks) + { + itemLink.Item.SmallImageIndex = 0; + } + } + selectedLink.Group.SmallImageIndex = 1; + selectedLink.Item.SmallImageIndex = 1; + } + + + private void ParamsConfigForm_Load(object sender, EventArgs e)//加载窗体时,读取配置文件夹,获取参数配置方案 + { + try + { + foreach (var planCollection in ConfigFactory.ParamsPlanColls) + { + var navBarGroup = new NavBarGroup(planCollection.PlanType); + this.navBarCtrlPlan.Groups.Add(navBarGroup); + foreach (var plan in planCollection) + { + var navBarItem = new NavBarItem { Caption = plan.Name, Tag = plan.FilePath }; + navBarItem.LinkClicked += planNavBarItem_LinkClicked; + this.navBarCtrlPlan.Items.Add(navBarItem); + navBarGroup.ItemLinks.Add(navBarItem); + } + } + this.navBarCtrlPlan.SelectedLink = this.navBarCtrlPlan.Groups[0].ItemLinks[0]; + this.navBarCtrlPlan.SelectedLink.PerformClick(); + } + catch (Exception ex) { MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } + } + + private void planNavBarItem_LinkClicked(object sender, NavBarLinkEventArgs e)//单击参数方案,加载参数配置库信息 + { + ResetImageOfItemsLinks(this.navBarCtrlPlan, e.Link); + this.navBarCtrlParams.Groups.Clear(); + this.navBarCtrlParams.Items.Clear(); + + try + { + var paramsPlanColl = ConfigFactory.ParamsPlanColls.FirstOrDefault(v => v.PlanType == e.Link.Group.Caption); + var classifies = paramsPlanColl[e.Link.Caption].Classifies; + classifies.ToList().ForEach(classify => + { + var navBarGroup = new NavBarGroup(classify.ClassifyName); + foreach (var paramsItem in classify.ParamsItems) + { + var navBarItem = new NavBarItem { Caption = paramsItem }; + navBarItem.LinkClicked += showFormNavBarItem_LinkClicked; + this.navBarCtrlPlan.Items.Add(navBarItem); + navBarGroup.ItemLinks.Add(navBarItem); + } + this.navBarCtrlParams.Groups.Add(navBarGroup); + }); + this.navBarCtrlParams.SelectedLink = this.navBarCtrlParams.Groups[0].ItemLinks[0]; + this.navBarCtrlParams.SelectedLink.PerformClick(); + } + catch (Exception ex) { MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } + } + + private void showFormNavBarItem_LinkClicked(object sender, NavBarLinkEventArgs e)//单击参数分类,打开对应的窗口 + { + ResetImageOfItemsLinks(this.navBarCtrlParams, e.Link); + Type formType = null; + switch (e.Link.Group.Caption) + { + case Group_DBHF: formType = typeof(EvaluationIndexForm); break; + case Group_PJZS: formType = typeof(EvaluationIndexForm); break; + case Group_XZQH: formType = typeof(EvaluationIndexForm); break; + case Group_ZWFQ: formType = typeof(EvaluationIndexForm); break; + } + this.labelControlTips.Text = e.Link.Caption; + + this.panelCtrlContent.ShowSubForm(formType); + } + + private void NavBarCtrl_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)//单击NavBarCtrl分组,加载相应图标 + { + if (e.Button != System.Windows.Forms.MouseButtons.Left) + return; + + var navBarCtrl = sender as NavBarControl; + var hitInfo = navBarCtrl.CalcHitInfo(new Point(e.X, e.Y)); + if (hitInfo.InGroupCaption && !hitInfo.InGroupButton) + { + hitInfo.Group.Expanded = !hitInfo.Group.Expanded; + if (hitInfo.Group.ItemLinks.Count > 0) + { + ResetImageOfItemsLinks(navBarCtrl, hitInfo.Group.ItemLinks[0]); + hitInfo.Group.ItemLinks[0].PerformClick(); + } + } + } + } +} diff --git a/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.resx b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.resx new file mode 100644 index 0000000000000000000000000000000000000000..d2d5b6881d939fda69cfb229887204e7bc7321d6 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Config/ParamsConfig/ParamsConfigForm.resx @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 8, 8 + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFpEZXZFeHByZXNzLlV0aWxzLnYxMC4xLCBWZXJzaW9uPTEwLjEu + Ni4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI4OGQxNzU0ZDcwMGU0OWEMAwAAAFFT + eXN0ZW0uRHJhd2luZywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRv + a2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAAChEZXZFeHByZXNzLlV0aWxzLkltYWdlQ29sbGVjdGlvblN0 + cmVhbWVyAgAAAAlJbWFnZVNpemUERGF0YQQHE1N5c3RlbS5EcmF3aW5nLlNpemUDAAAAAgIAAAAF/P// + /xNTeXN0ZW0uRHJhd2luZy5TaXplAgAAAAV3aWR0aAZoZWlnaHQAAAgIAwAAABAAAAAQAAAACQUAAAAP + BQAAAEsDAAAC2gAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB8SURBVDhPYxjuIHHDfzgmGSBrJMkQmGJ0DbjEUUDixuUY + ChI2/oKyIABiyHkoDw3Ur2JDMQBmI7oYXgBTALIZxibagKRNbhiKYRgGQOzk9WVQHhpIXP8DRTE2AJKP + Xy8A5WEBMBtBGBQmMIAsThRA1kCSRmRAkWb6AAYGAE/CbObiAuWxAAAAAElFTkSuQmCC3QAAAIlQTkcN + ChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7D + AcdvqGQAAAB/SURBVDhPtYvBCYAwEASDRYndqPgS7MGOoj6sxjb8K0qi5jKXvBwYOHb3zD+086GahB5I + hIYpI2iU84FKMtpO+52FoaInysexiEKpB7tm2rDweqi7NJ0tw8Ahb00cfJGdVB1pufSmWwYscwbQIKnt + 3ecHHKKr+wDqpYKH1wBjTp3zcRuydkKEAAAAAElFTkSuQmCCtwAAAIlQTkcNChoKAAAADUlIRFIAAAAQ + AAAAEAgGAAAAH/P/YQAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABZSURBVDhP + YxhEIHHDf5IxCsCmgBBOXu8A1Q0EMEF8AFkzCGM1gBRM0AUJ6w+gaEDHtHcBOkDWDML0DQOCAKYQF0ZW + AzOY6DCAicPU4DUAF0ZWQxsDyAIMDABK3SOAHH5oEAAAAABJRU5ErkJggs0AAACJUE5HDQoaCgAAAA1J + SERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA + b0lEQVQ4T7WSwQ2AIBAEL5Zhd/i1CEuSLiwLc8QzAebkPk4yH2Z5kCD/sOVVUi6DR1mexQd0sdeFxp4D + NFJnrbI7b1YVOldfKJqRjsGMdAxmpGMwIx1D1Ir+MIozG2igzloDjdJ54bkLjXtDhC6K3EbvY9hK9Wjl + AAAAAElFTkSuQmCCCw== + + + + 39 + + \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/Properties/AssemblyInfo.cs b/YYGDZL.Bussiness.WinForm/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..db9190ac506af92bcc316653b40a1dbe8cf8eb47 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("YYGDZL.Bussiness.WinForm")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("YYGDZL.Bussiness.WinForm")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("7591876a-b933-4372-a09b-780a30abb40e")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/YYGDZL.Bussiness.WinForm/Properties/Resources.Designer.cs b/YYGDZL.Bussiness.WinForm/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..6316557b2db67e9828ddcf0fb0e0dbc0f9c60d3b --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace YYGDZL.Business.WinForm.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("YYGDZL.Business.WinForm.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/YYGDZL.Bussiness.WinForm/Properties/Resources.resx b/YYGDZL.Bussiness.WinForm/Properties/Resources.resx new file mode 100644 index 0000000000000000000000000000000000000000..1af7de150c99c12dd67a509fe57c10d63e4eeb04 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/YYGDZL.Bussiness.WinForm/YYGDZL.Business.WinForm.csproj b/YYGDZL.Bussiness.WinForm/YYGDZL.Business.WinForm.csproj new file mode 100644 index 0000000000000000000000000000000000000000..172c4824396ddab17364a106e4ef5b9e84f17012 --- /dev/null +++ b/YYGDZL.Bussiness.WinForm/YYGDZL.Business.WinForm.csproj @@ -0,0 +1,111 @@ + + + + + Debug + AnyCPU + {7591876A-B933-4372-A09B-780A30ABB40E} + Library + Properties + YYGDZL.Business.WinForm + YYGDZL.Business.WinForm + v4.5 + 512 + true + + + true + full + false + ..\Apps\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + EvaluationIndexForm.cs + + + Form + + + ParamsConfigForm.cs + + + + True + True + Resources.resx + + + + + EvaluationIndexForm.cs + + + ParamsConfigForm.cs + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {0e418c82-3534-4604-bee4-4e5bd0bb926d} + WLib.ArcGis + + + {5ac4fbca-7dc4-49ad-b3ef-f5a7ba5a5a47} + WLib.UserCtrls.Dev + + + {69d92973-b7bc-4c0b-8038-f9063d2eb2a1} + WLib + + + {0c939673-21a7-468f-8f83-aa1227cbc558} + YYGDZL.Business.Base + + + + + + + + + \ No newline at end of file diff --git "a/\350\200\225\345\234\260\350\264\250\351\207\217\347\256\241\347\220\206\345\271\263\345\217\260.sln" "b/\350\200\225\345\234\260\350\264\250\351\207\217\347\256\241\347\220\206\345\271\263\345\217\260.sln" new file mode 100644 index 0000000000000000000000000000000000000000..1f6d07e638c35ff2bcea04f2e6b4ea7c33f595e9 --- /dev/null +++ "b/\350\200\225\345\234\260\350\264\250\351\207\217\347\256\241\347\220\206\345\271\263\345\217\260.sln" @@ -0,0 +1,161 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib", "WLib\WLib.csproj", "{69D92973-B7BC-4C0B-8038-F9063D2EB2A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.ArcGis", "WLib.ArcGis\WLib.ArcGis.csproj", "{0E418C82-3534-4604-BEE4-4E5BD0BB926D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.Envir", "WLib.Envir\WLib.Envir.csproj", "{32FB07DD-7400-4011-BEBC-72ED503B4911}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.UserCtrls.Dev", "WLib.UserCtrl.Dev\WLib.UserCtrls.Dev.csproj", "{5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{5130DE59-664E-4262-A66B-843EF83DB399}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.Files.Excel", "WLib.Files.Excel\WLib.Files.Excel.csproj", "{AEB9C1BE-96CF-42F5-AB08-7A45817CF906}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.Files.Word", "WLib.Files.Word\WLib.Files.Word.csproj", "{140B142F-0A73-4561-91CA-2AD98CF38572}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "YYGDZL.Business", "YYGDZL.Business", "{251545C6-115E-4BC1-A966-7ACFCAA9FA17}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "YYGDZL.App", "YYGDZL.App", "{878C8532-49AC-447C-BF04-ABCC3882C436}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YYGDZL.App.DBPJ", "YYGDZL.Business.DBPJ\YYGDZL.App.DBPJ.csproj", "{0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YYGDZL.Business.Base", "YYGDZL.Business.Base\YYGDZL.Business.Base.csproj", "{0C939673-21A7-468F-8F83-AA1227CBC558}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper", "Dapper\Dapper.csproj", "{B69EA382-D83F-481F-88B9-F0AC9DBCE928}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WLib.UserCtrls", "WLib.UserCtrl\WLib.UserCtrls.csproj", "{854A8E9D-31D4-4BC5-89FF-703318999065}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YYGDZL.Business.WinForm", "YYGDZL.Bussiness.WinForm\YYGDZL.Business.WinForm.csproj", "{7591876A-B933-4372-A09B-780A30ABB40E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FtpClient", "FtpClient(jb51.net)\FtpClient.csproj", "{4A235456-646E-4D5C-810B-89FE25717B36}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Debug|x86.ActiveCfg = Debug|x86 + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Debug|x86.Build.0 = Debug|x86 + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Release|Any CPU.Build.0 = Release|Any CPU + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Release|x86.ActiveCfg = Release|x86 + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1}.Release|x86.Build.0 = Release|x86 + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Debug|x86.ActiveCfg = Debug|x86 + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Debug|x86.Build.0 = Debug|x86 + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Release|Any CPU.Build.0 = Release|Any CPU + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Release|x86.ActiveCfg = Release|x86 + {0E418C82-3534-4604-BEE4-4E5BD0BB926D}.Release|x86.Build.0 = Release|x86 + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Debug|x86.ActiveCfg = Debug|x86 + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Debug|x86.Build.0 = Debug|x86 + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Release|Any CPU.Build.0 = Release|Any CPU + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Release|x86.ActiveCfg = Release|x86 + {32FB07DD-7400-4011-BEBC-72ED503B4911}.Release|x86.Build.0 = Release|x86 + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Debug|x86.ActiveCfg = Debug|x86 + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Debug|x86.Build.0 = Debug|x86 + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Release|Any CPU.Build.0 = Release|Any CPU + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Release|x86.ActiveCfg = Release|x86 + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47}.Release|x86.Build.0 = Release|x86 + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Debug|x86.ActiveCfg = Debug|x86 + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Debug|x86.Build.0 = Debug|x86 + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Release|Any CPU.Build.0 = Release|Any CPU + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Release|x86.ActiveCfg = Release|x86 + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906}.Release|x86.Build.0 = Release|x86 + {140B142F-0A73-4561-91CA-2AD98CF38572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {140B142F-0A73-4561-91CA-2AD98CF38572}.Debug|Any CPU.Build.0 = Debug|Any CPU + {140B142F-0A73-4561-91CA-2AD98CF38572}.Debug|x86.ActiveCfg = Debug|x86 + {140B142F-0A73-4561-91CA-2AD98CF38572}.Debug|x86.Build.0 = Debug|x86 + {140B142F-0A73-4561-91CA-2AD98CF38572}.Release|Any CPU.ActiveCfg = Release|Any CPU + {140B142F-0A73-4561-91CA-2AD98CF38572}.Release|Any CPU.Build.0 = Release|Any CPU + {140B142F-0A73-4561-91CA-2AD98CF38572}.Release|x86.ActiveCfg = Release|x86 + {140B142F-0A73-4561-91CA-2AD98CF38572}.Release|x86.Build.0 = Release|x86 + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Debug|x86.Build.0 = Debug|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Release|Any CPU.Build.0 = Release|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Release|x86.ActiveCfg = Release|Any CPU + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C}.Release|x86.Build.0 = Release|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Debug|x86.Build.0 = Debug|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Release|Any CPU.Build.0 = Release|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Release|x86.ActiveCfg = Release|Any CPU + {0C939673-21A7-468F-8F83-AA1227CBC558}.Release|x86.Build.0 = Release|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Debug|x86.ActiveCfg = Debug|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Debug|x86.Build.0 = Debug|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Release|Any CPU.Build.0 = Release|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Release|x86.ActiveCfg = Release|Any CPU + {B69EA382-D83F-481F-88B9-F0AC9DBCE928}.Release|x86.Build.0 = Release|Any CPU + {854A8E9D-31D4-4BC5-89FF-703318999065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {854A8E9D-31D4-4BC5-89FF-703318999065}.Debug|Any CPU.Build.0 = Debug|Any CPU + {854A8E9D-31D4-4BC5-89FF-703318999065}.Debug|x86.ActiveCfg = Debug|x86 + {854A8E9D-31D4-4BC5-89FF-703318999065}.Debug|x86.Build.0 = Debug|x86 + {854A8E9D-31D4-4BC5-89FF-703318999065}.Release|Any CPU.ActiveCfg = Release|Any CPU + {854A8E9D-31D4-4BC5-89FF-703318999065}.Release|Any CPU.Build.0 = Release|Any CPU + {854A8E9D-31D4-4BC5-89FF-703318999065}.Release|x86.ActiveCfg = Release|x86 + {854A8E9D-31D4-4BC5-89FF-703318999065}.Release|x86.Build.0 = Release|x86 + {7591876A-B933-4372-A09B-780A30ABB40E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Debug|x86.ActiveCfg = Debug|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Debug|x86.Build.0 = Debug|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Release|Any CPU.Build.0 = Release|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Release|x86.ActiveCfg = Release|Any CPU + {7591876A-B933-4372-A09B-780A30ABB40E}.Release|x86.Build.0 = Release|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Debug|x86.ActiveCfg = Debug|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Debug|x86.Build.0 = Debug|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Release|Any CPU.Build.0 = Release|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Release|x86.ActiveCfg = Release|Any CPU + {4A235456-646E-4D5C-810B-89FE25717B36}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {69D92973-B7BC-4C0B-8038-F9063D2EB2A1} = {5130DE59-664E-4262-A66B-843EF83DB399} + {0E418C82-3534-4604-BEE4-4E5BD0BB926D} = {5130DE59-664E-4262-A66B-843EF83DB399} + {32FB07DD-7400-4011-BEBC-72ED503B4911} = {5130DE59-664E-4262-A66B-843EF83DB399} + {5AC4FBCA-7DC4-49AD-B3EF-F5A7BA5A5A47} = {5130DE59-664E-4262-A66B-843EF83DB399} + {AEB9C1BE-96CF-42F5-AB08-7A45817CF906} = {5130DE59-664E-4262-A66B-843EF83DB399} + {140B142F-0A73-4561-91CA-2AD98CF38572} = {5130DE59-664E-4262-A66B-843EF83DB399} + {0D21A25A-06C7-48DC-A513-1A5E9DA99B5C} = {878C8532-49AC-447C-BF04-ABCC3882C436} + {0C939673-21A7-468F-8F83-AA1227CBC558} = {251545C6-115E-4BC1-A966-7ACFCAA9FA17} + {B69EA382-D83F-481F-88B9-F0AC9DBCE928} = {5130DE59-664E-4262-A66B-843EF83DB399} + {854A8E9D-31D4-4BC5-89FF-703318999065} = {5130DE59-664E-4262-A66B-843EF83DB399} + {7591876A-B933-4372-A09B-780A30ABB40E} = {251545C6-115E-4BC1-A966-7ACFCAA9FA17} + {4A235456-646E-4D5C-810B-89FE25717B36} = {878C8532-49AC-447C-BF04-ABCC3882C436} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C4D3EFBB-9208-45D9-8A8D-65C44E9FCEDA} + EndGlobalSection +EndGlobal