예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <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);
            }
        }