/// <summary> /// 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. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="target">The object whose property/field you wish to populate.</param> /// <param name="expression">A MemberExpression targeting a property/field of the target (or descendant thereof.)</param> /// <param name="dbType"></param> /// <param name="size">The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings.</param> /// <returns>The DynamicParameters instance</returns> public DynamicParameters Output <T>(T target, Expression <Func <T, object> > 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 <string> names = new List <string>(); List <MemberExpression> chain = new List <MemberExpression>(); 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 <T> .Cache; var setter = (Action <object, DynamicParameters>)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 <object, DynamicParameters>)dm.CreateDelegate(typeof(Action <object, DynamicParameters>)); lock (cache) { cache[lookup] = setter; } // Queue the preparation to be fired off when adding parameters to the DbCommand MAKECALLBACK: (outputCallbacks ?? (outputCallbacks = new List <Action>())).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); }
/// <summary> /// Add all the parameters needed to the command just before it executes /// </summary> /// <param name="command">The raw command prior to execution</param> /// <param name="identity">Information about the query</param> protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { var literals = SqlMapper.GetLiteralTokens(identity.sql); if (templates != null) { foreach (var template in templates) { var newIdent = identity.ForDynamicParameters(template.GetType()); Action <IDbCommand, object> appender; lock (paramReaderCache) { if (!paramReaderCache.TryGetValue(newIdent, out appender)) { appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals); paramReaderCache[newIdent] = appender; } } appender(command, template); } // 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); } }