static LambdaExpression CreateMappingLambda(Type typeT, List <Mapping <Thing, Thing> > mapping)
        {
            var result          = Expression.Parameter(typeof(SqlDataRecord), "rec");
            var metaDataParam   = Expression.Parameter(typeof(SqlMetaData[]), "metaData");
            var item            = Expression.Parameter(typeT, "item");
            var constructorInfo = typeof(SqlDataRecord).GetConstructor(new[] { typeof(SqlMetaData[]) });
            var lines           = new List <Expression>
            {
                Expression.Assign(result, Expression.New(constructorInfo, metaDataParam))
            };
            var propertiesAndFields = Types.ReadablePropertiesAndFieldsDictionary(typeT);

            var setNullMethod = typeof(SqlDataRecord).GetMethod("SetDBNull", new[] { typeof(int) });

            Contract.Assert(setNullMethod != null);

            foreach (var map in mapping)
            {
                var col         = (Column)map.To;
                var setValueExp = SetValue(result, item, map.From, col);
                if (setValueExp == null)
                {
                    continue;
                }

                if (Types.CanBeNull(map.From.Type))
                {
                    lines.Add(Expression.IfThenElse(
                                  Expression.Equal(Expression.PropertyOrField(item, map.From.Name), Expression.Constant(null)),
                                  Expression.Call(result, setNullMethod, Expression.Constant(col.Ordinal)),
                                  setValueExp
                                  ));
                }
                else
                {
                    lines.Add(setValueExp);
                }
            }
            lines.Add(result);
            var block            = Expression.Block(new[] { result }, lines);
            var func             = typeof(Func <, ,>).MakeGenericType(typeof(SqlMetaData[]), typeT, typeof(SqlDataRecord));
            var lambdaExpression = Expression.Lambda(func, block, metaDataParam, item);

            return(lambdaExpression);
        }
        static LambdaExpression CreatePrimativeLambda(Type typeT, Column col)
        {
            var result          = Expression.Parameter(typeof(SqlDataRecord), "rec");
            var metaDataParam   = Expression.Parameter(typeof(SqlMetaData[]), "metaData");
            var item            = Expression.Parameter(typeT, "item");
            var constructorInfo = typeof(SqlDataRecord).GetConstructor(new[] { typeof(SqlMetaData[]) });
            var lines           = new List <Expression>
            {
                Expression.Assign(result, Expression.New(constructorInfo, metaDataParam))
            };

            var setNullMethod = typeof(SqlDataRecord).GetMethod("SetDBNull", new[] { typeof(int) });

            Contract.Assert(setNullMethod != null);

            var setValueExp = SetValue(result, col.Type, col, item);

            if (setValueExp == null)
            {
                throw new InvalidOperationException($"Cannot map from {typeT} to {col.Type}");
            }

            if (Types.CanBeNull(typeT))
            {
                lines.Add(Expression.IfThenElse(
                              Expression.Equal(item, Expression.Constant(null)),
                              Expression.Call(result, setNullMethod, Expression.Constant(col.Ordinal)),
                              setValueExp
                              ));
            }
            else
            {
                lines.Add(setValueExp);
            }
            lines.Add(result);
            var block            = Expression.Block(new[] { result }, lines);
            var func             = typeof(Func <, ,>).MakeGenericType(typeof(SqlMetaData[]), typeT, typeof(SqlDataRecord));
            var lambdaExpression = Expression.Lambda(func, block, metaDataParam, item);

            return(lambdaExpression);
        }
        static Action <DbCommand, object> CreateAddParametersAction(Type type)
        {
            var obj        = Expression.Parameter(typeof(object), "obj");
            var parameters = Expression.Parameter(type, "parameters");
            var cmd        = Expression.Parameter(typeof(DbCommand), "cmd");
            var dataParam  = Expression.Parameter(typeof(DbParameter), "dataParam");
            var lines      = new List <Expression>
            {
                Expression.Assign(parameters, Expression.Convert(obj, type))
            };
            var createParameter = typeof(DbCommand).GetMethod("CreateParameter", Type.EmptyTypes);

            foreach (var prop in Types.ReadablePublicFieldsAndProperties(type))
            {
                var fieldOrProperty = Types.PropertyOrFieldType(prop);
                if (!IsSupportedType(fieldOrProperty))
                {
                    continue;
                }
                lines.Add(Expression.Assign(dataParam, Expression.Call(cmd, createParameter)));
                lines.Add(Expression.Assign(Expression.Property(dataParam, "ParameterName"), Expression.Constant("@" + prop.Name)));

                if (Types.IsStructured(fieldOrProperty))
                {
                    if (fieldOrProperty != typeof(SqlTable))
                    {
                        throw new NotSupportedException($"Parameter {dataParam.Name} implements {nameof(IEnumerable<SqlDataRecord>)} but type name is unknown.  Please wrap parameter by calling {nameof(Extensions.WithTypeName)}");
                    }

                    lines.Add(Expression.IfThen(
                                  Expression.Not(Expression.TypeIs(cmd, typeof(DbCommand))),
                                  Expression.Throw(Expression.New(typeof(NotSupportedException).GetConstructor(new[] { typeof(string) }), Expression.Constant("Structured parameters are supported only for SqlCommand")))
                                  ));
                    lines.Add(Expression.Assign(Expression.Property(Expression.Convert(dataParam, typeof(SqlParameter)), "SqlDbType"), Expression.Constant(SqlDbType.Structured)));
                    lines.Add(Expression.Assign(Expression.Property(Expression.Convert(dataParam, typeof(SqlParameter)), "TypeName"), Expression.Property(Expression.Property(parameters, prop.Name), "TypeName")));
                }
                else if (fieldOrProperty.IsEnum || Types.IsNullableEnum(fieldOrProperty))
                {
                    lines.Add(Expression.Assign(Expression.Property(dataParam, "DbType"), Expression.Constant(DbType.Int32)));
                }
                else
                {
                    lines.Add(Expression.Assign(Expression.Property(dataParam, "DbType"), Expression.Constant(Types.TypeToDbType[fieldOrProperty])));
                }

                if (fieldOrProperty == typeof(SqlTable))
                {
                    // check if records is null
                    var tt      = Expression.PropertyOrField(parameters, prop.Name);
                    var records = Expression.Property(Expression.Convert(tt, typeof(SqlTable)), "Records");
                    lines.Add(
                        Expression.Assign(Expression.Property(dataParam, "Value"), Expression.Convert(records, typeof(object))
                                          //Expression.Condition(
                                          //    Expression.Equal(records, Expression.Constant(null)),
                                          //    Expression.Convert(Expression.Field(null, typeof(DBNull), "Value"), typeof(object)),
                                          //    Expression.Convert(records, typeof(object))
                                          //)
                                          )
                        );
                }
                else if (Types.CanBeNull(fieldOrProperty))
                {
                    lines.Add(
                        Expression.Assign(
                            Expression.Property(dataParam, "Value"),
                            Expression.Condition(
                                Expression.Equal(Expression.Property(parameters, prop.Name), Expression.Constant(null)),
                                Expression.Convert(Expression.Field(null, typeof(DBNull), "Value"), typeof(object)),
                                PropertyValueCastToObject(parameters, prop)
                                )
                            )
                        );
                }
                else
                {
                    lines.Add(Expression.Assign(Expression.Property(dataParam, "Value"), PropertyValueCastToObject(parameters, prop)));
                }

                lines.Add(Expression.Call(Expression.Property(cmd, "Parameters"), typeof(DbParameterCollection).GetMethod("Add", new[] { typeof(object) }), dataParam));
            }
            var block = Expression.Block(new[] { dataParam, parameters }, lines);

            return(Expression.Lambda <Action <DbCommand, object> >(block, cmd, obj).Compile());
        }