Inheritance: BaseCommand
 private static void AppendUpdateCommand(Script script, UpdateCommand command, ref int paramIndex)
 {
     if (ReverseUpdateHelper.IsReverseUpdateWithChildReferencingParent(command.Operations.First()))
     {
         AppendReverseUpdateCommandForChildTableReferencingParent(script, command, ref paramIndex);
     }
     else
     {
         AppendStandardUpdateCommand(script, command, ref paramIndex);
     }
 }
        private static void AppendReverseUpdateCommandForChildTableReferencingParent(
            Script script,
            UpdateCommand command,
            ref int paramIndex)
        {
            var operation = command.Operations.FirstOrDefault();
            var fkPropertyMetadata = operation.ValueMetadata.GetForeignKeyColumnFor(
                operation.OwnerMetadata.DtoType);
            script.Buffer.Append(string.Format(@"UPDATE {0}
SET [{1}] = ",
                operation.ValueMetadata.TableName,
                fkPropertyMetadata.ColumnName));

            var valueFunc = new Func<object>(() => command.PrimaryKeyAsObject);
            var wireUpAction = new Action(() =>
            {
                object newPropertyValue = valueFunc();
                if (CanAssignToProperty(newPropertyValue, fkPropertyMetadata))
                {
                    //  This will wire the correct FK column value into the child object.
                    fkPropertyMetadata.Prop.SetValue(operation.Value, newPropertyValue);
                }
            });
            FormatWithParameter(
                script,
                "{0}",
                ref paramIndex,
                wireUpAction,
                valueFunc);

            script.Buffer.Append(string.Format(@"
WHERE [{0}] = ", operation.ValueMetadata.PrimaryKey.ColumnName));

            FormatWithParameter(script, @"{0};
",
                ref paramIndex,
                null,   //  Don't need to do any wire up for this since we already have the PK on the child.
                operation.Value is Func<object>
                    ? operation.Value
                    : new Func<object>(() => operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
        }
        private void ApplyUpdatesSoFarAsNewCommand(
            IList <BaseCommand> results,
            IList <UpdateOperation> updates,
            ref string updateTableName,
            ref object updatePk)
        {
            if (updates.Count == 0)
            {
                return;
            }
            var command = new UpdateCommand();

            foreach (var update in updates)
            {
                command.AddOperation(update);
            }
            results.Add(command);
            updates.Clear();
            updateTableName = null;
            updatePk        = null;
        }
        private static void AppendReverseUpdateCommandForChildTableReferencingParent(
            Script script,
            UpdateCommand command,
            ref int paramIndex)
        {
            var operation = command.Operations.FirstOrDefault();
            script.Buffer.Append(string.Format(@"UPDATE {0}
SET [{1}] = ",
                operation.ValueMetadata.TableName,
                operation.ValueMetadata.GetForeignKeyColumnFor(
                    operation.OwnerMetadata.DtoType).ColumnName));

            FormatWithParameter(
                script,
                "{0}",
                ref paramIndex,
                new Func<object>(() => command.PrimaryKeyAsObject));

            script.Buffer.Append(string.Format(@"
WHERE [{0}] = ", operation.ValueMetadata.PrimaryKey.ColumnName));

            FormatWithParameter(script, @"{0};
", ref paramIndex, new Func<object>(() => operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
        }
        private void AppendPropertyToInsertStatement(
            Script script,
            StringBuilder colBuff,
            StringBuilder valBuff,
            PropertyMetadata property,
            ref int index,
            BaseInsertDeleteOperation operation,
            ArrayList values,
            MethodInfo getter,
            UpdateCommand updateCommand)
        {
            object columnValueForUpdate = null;

            if (property.HasAttribute <ForeignKeyReferenceAttribute>() &&
                null != operation.OwnerMetadata &&
                _dtoMetadataCache.GetValidatedMetadataFor(
                    property.GetAttribute <ForeignKeyReferenceAttribute>().ReferencedDto).TableName ==
                operation.OwnerMetadata.TableName)
            {
                columnValueForUpdate = new Func <object>(() => operation.OwnerPrimaryKeyAsObject);
                values.Add(columnValueForUpdate);
            }
            else if (property.HasAttribute <ManyToOneAttribute>() && property.GetAttribute <ManyToOneAttribute>().ForeignKeyTargetColumnName != null)
            {
                var propValue        = property.GetValue(operation.Value);
                var propTypeMetadata = _dtoMetadataCache.GetValidatedMetadataFor(property.Prop.PropertyType);
                if (null != propValue && null != propTypeMetadata)
                {
                    var targetName       = property.GetAttribute <ManyToOneAttribute>().ForeignKeyTargetColumnName;
                    var fkTargetProperty = propTypeMetadata[targetName];
                    if (fkTargetProperty == null)
                    {
                        throw new ArgumentException(string.Format(
                                                        "Cannot INSERT foreign key value for non existent target column '{0}'"
                                                        + " specified from column '{1}'.",
                                                        targetName,
                                                        property.ColumnName));
                    }

                    columnValueForUpdate = new Func <object>(() => fkTargetProperty.GetValue(propValue));
                    values.Add(columnValueForUpdate);
                }
                else
                {
                    columnValueForUpdate = new Func <object>(() => null);
                    values.Add(columnValueForUpdate);
                }
            }
            else if (property.HasAttribute <ManyToOneAttribute>() || property.HasAttribute <OneToOneAttribute>())
            {
                if (property.HasAttribute <OneToOneAttribute>() && !property.HasAttribute <ForeignKeyReferenceAttribute>())
                {
                    //  One to one relationship where child table references parent rather than the other way around.
                    //  This will be saved along with the child object.
                    return;
                }

                object      propValue    = property.GetValue(operation.Value);
                DtoMetadata propMetadata = _dtoMetadataCache.GetValidatedMetadataFor(property.Prop.PropertyType);
                columnValueForUpdate = new Func <object>(
                    () =>
                    propValue == null || propMetadata == null
                                ? null
                                : propMetadata.GetPrimaryKeyValueAsObject(propValue));
                values.Add(columnValueForUpdate);
            }
            else
            {
                columnValueForUpdate = getter.Invoke(operation.Value, new object[0]);
                values.Add(columnValueForUpdate);
            }

            if (columnValueForUpdate is Func <object> )
            {
                script.WireUpActions.Add(() =>
                {
                    object newPropertyValue = ((Func <object>)columnValueForUpdate)();
                    if (CanAssignToProperty(newPropertyValue, property))
                    {
                        property.Prop.SetValue(operation.Value, newPropertyValue);
                    }
                });
            }

            updateCommand.AddOperation(new UpdateOperation()
            {
                ColumnPropertyMetadata = property,
                TableName     = operation.ValueMetadata.TableName,
                Value         = columnValueForUpdate,
                ValueMetadata = property.IsString || property.IsNumericType || property.IsEnum || !property.IsReferenceType
                    ? null
                    : _dtoMetadataCache.GetMetadataFor(property.Prop.PropertyType),
                Owner                 = operation.Value,
                OwnerMetadata         = operation.ValueMetadata,
                OwnerPrimaryKeyColumn = operation.ValueMetadata.PrimaryKey.ColumnName,
                OwnerPropertyMetadata = property
            });

            if (colBuff.Length > 0)
            {
                colBuff.Append(@", ");
                valBuff.Append(@", ");
            }

            colBuff.Append("[" + property.ColumnName + "]");

            valBuff.Append("{");
            valBuff.Append(index);
            valBuff.Append("}");

            ++index;
        }
        private bool AppendInsertCommand(Script script, InsertCommand command, ref int paramIndex)
        {
            var isPkAssignedByRdbms       = true;
            var needsUpdateContingency    = false;
            PropertyMetadata guidPKColumn = null;
            var operation = command.Operation;

            if (operation.ValueMetadata != null)
            {
                if (null != operation.OwnerPropertyMetadata &&
                    operation.OwnerPropertyMetadata.HasAttribute <ManyToManyAttribute>())
                {
                    InsertRecordInLinkTable(script, ref paramIndex, operation);
                    script.Buffer.Append(@"
SELECT SCOPE_IDENTITY();
");
                }
                else if (operation.OwnerPropertyMetadata == null ||
                         ((operation.OwnerPropertyMetadata.HasAttribute <OneToManyAttribute>() ||
                           operation.OwnerPropertyMetadata.HasAttribute <OneToOneAttribute>()) &&//IsOneToOneRelationshipWithFkOnParent(operation)) //  Because 1:1 with FK on child is like 1:N, and we already handle 1:1 with FK on parent anyway
                          !operation.ValueMetadata.HasAttribute <ReferenceDataAttribute>() &&
                          !operation.OwnerPropertyMetadata.HasAttribute <ReferenceDataAttribute>()))
                {
                    //  INSERT the value into the table defined by ValueMetadata

                    var colBuff = new StringBuilder();
                    var valBuff = new StringBuilder();
                    var values  = new ArrayList();
                    var index   = 0;

                    var    updateCommand             = new UpdateCommand();
                    var    hasPrimaryKeyValueAlready = false;
                    string pkColumnName = null;

                    var columnsSeenBefore = CreateColumnsSeenBefore();
                    foreach (var property in operation.ValueMetadata.WriteableProperties)
                    {
                        if (property.IsPrimaryKey)
                        {
                            isPkAssignedByRdbms = !property.GetAttribute <PrimaryKeyAttribute>().IsUserAssigned;
                            pkColumnName        = property.ColumnName;

                            var pkValue = operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value);
                            if (pkValue != null)
                            {
                                hasPrimaryKeyValueAlready = true;
                            }

                            //updateCommand.AddOperation(new UpdateOperation()
                            //{
                            //    ColumnName = property.ColumnName,
                            //    TableName = operation.ValueMetadata.TableName,
                            //    Value = pkValue,
                            //    ValueMetadata = _dtoMetadataCache.GetMetadataFor(property.Prop.PropertyType),
                            //    Owner = operation.Owner,
                            //    OwnerMetadata = operation.OwnerMetadata,
                            //    OwnerPrimaryKeyColumn = operation.ValueMetadata.PrimaryKey.ColumnName,
                            //    OwnerPropertyMetadata = property
                            //});

                            if (isPkAssignedByRdbms)
                            {
                                var type = property.Prop.PropertyType;
                                if (type != typeof(int) &&
                                    type != typeof(int?) &&
                                    type != typeof(long) &&
                                    type != typeof(long?))
                                {
                                    if (property.Prop.PropertyType == typeof(Guid?) ||
                                        property.Prop.PropertyType == typeof(Guid))
                                    {
                                        guidPKColumn = property;
                                    }
                                    else
                                    {
                                        throw new ArgumentException(string.Format(
                                                                        "Unsupported primary key type {0} on entity {1}. Primary keys must be nullable ints, longs or GUIDs.",
                                                                        type.FullName,
                                                                        operation.ValueMetadata.DtoType.FullName));
                                    }
                                }

                                continue;
                            }
                        }

                        var getter = property.Prop.GetGetMethod();

                        if (getter == null ||
                            property.HasAttribute <ManyToManyAttribute>() ||
                            property.HasAttribute <OneToManyAttribute>() ||
                            !property.IsSaveable)
                        {
                            continue;
                        }

                        //updateCommand.AddOperation(new UpdateOperation()
                        //{
                        //    ColumnName = property.ColumnName,
                        //    TableName = operation.ValueMetadata.TableName,
                        //    Value = operation.Value,
                        //    ValueMetadata = operation.ValueMetadata,
                        //    Owner = operation.Owner,
                        //    OwnerMetadata = operation.OwnerMetadata,
                        //    OwnerPrimaryKeyColumn = operation.OwnerPrimaryKeyColumn,
                        //    OwnerPropertyMetadata = operation.OwnerPropertyMetadata
                        //});

                        CheckAndThrowIfThisColumnHasBeenSeenBefore(
                            columnsSeenBefore, operation.ValueMetadata.TableName, property.ColumnName, command);

                        AppendPropertyToInsertStatement(
                            script, colBuff, valBuff, property, ref index, operation, values, getter, updateCommand);
                    }

                    needsUpdateContingency = !isPkAssignedByRdbms || hasPrimaryKeyValueAlready;

                    if (needsUpdateContingency)
                    {
                        script.Buffer.Append(
                            string.Format(@"IF EXISTS (SELECT * FROM {0} WHERE [{1}] = ",
                                          operation.ValueMetadata.TableName,
                                          pkColumnName));

                        //  N.B. The PK value doesn't need referencing with a function here because
                        //  we already have a primary key value.
                        FormatWithParameter(
                            script,
                            "{0}",
                            ref paramIndex,
                            null, //  No wire-up required for the IF EXISTS value
                            operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value));

                        script.Buffer.Append(@")
BEGIN
");
                        AppendUpdateCommand(script, updateCommand, ref paramIndex);

                        if (guidPKColumn == null)
                        {
                            script.Buffer.Append(string.Format(@"
SELECT {0};
", operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
                        }
                        else
                        {
                            script.Buffer.Append(string.Format(@"
SELECT '{0}';
", operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
                        }

                        script.Buffer.Append(@"
END
ELSE
BEGIN
");
                    }

                    script.Buffer.Append(string.Format(
                                             @"INSERT INTO {0} (
    {1}
)",
                                             operation.ValueMetadata.TableName,
                                             colBuff));

                    if (guidPKColumn != null && isPkAssignedByRdbms)
                    {
                        script.Buffer.Append(string.Format(@"
OUTPUT inserted.[{0}]
",
                                                           guidPKColumn.ColumnName));
                    }

                    script.Buffer.Append(@" VALUES (
    ");

                    FormatWithParameter(script, valBuff.ToString(), ref paramIndex, null, values.ToArray());
                    script.Buffer.Append(@"
);
");
                    script.InsertedValue         = operation.Value;
                    script.InsertedValueMetadata = operation.ValueMetadata;

                    if (guidPKColumn == null && isPkAssignedByRdbms)
                    {
                        script.Buffer.Append(@"
SELECT SCOPE_IDENTITY();
");
                    }

                    if (needsUpdateContingency)
                    {
                        script.Buffer.Append(@"
END
");
                    }
                }
            }
            else
            {
                throw new ArgumentException(
                          string.Format(
                              "Invalid INSERT command: {0}",
                              JsonConvert.SerializeObject(command)),
                          "command");
            }
            return(isPkAssignedByRdbms);
        }
        private static void AppendStandardUpdateCommand(
            Script script,
            UpdateCommand command,
            ref int paramIndex)
        {
            script.Buffer.Append(string.Format(@"UPDATE {0}
SET ", command.TableName));

            int count             = 0;
            var columnsSeenBefore = CreateColumnsSeenBefore();

            foreach (var operation in command.Operations)
            {
                if (count > 0)
                {
                    script.Buffer.Append(@",
    ");
                }

                CheckAndThrowIfThisColumnHasBeenSeenBefore(
                    columnsSeenBefore, command.TableName, operation.ColumnName, command);

                script.Buffer.Append(string.Format(@"[{0}] = ", operation.ColumnName));
                bool   useKey         = false;
                string fkTargetColumn = null;

                if (null != operation.ValueMetadata)
                {
                    if ((null != operation.OwnerPropertyMetadata &&
                         (operation.OwnerPropertyMetadata.HasAttribute <OneToOneAttribute>() ||
                          operation.OwnerPropertyMetadata.HasAttribute <ManyToOneAttribute>() ||
                          operation.OwnerPropertyMetadata.HasAttribute <ReferenceDataAttribute>())))
                    {
                        var manyToOne = operation.OwnerPropertyMetadata.GetAttribute <ManyToOneAttribute>();
                        if (manyToOne != null && manyToOne.ForeignKeyTargetColumnName != null)
                        {
                            fkTargetColumn = manyToOne.ForeignKeyTargetColumnName;
                        }
                        else
                        {
                            useKey = true;
                        }
                    }
                    else if (operation.ValueMetadata.HasAttribute <ReferenceDataAttribute>())
                    {
                        useKey = true;
                    }
                }

                if (useKey)
                {
                    var valueFunc = operation.Value == null
                        ? null
                        : (operation.Value is Func <object>
                           ?(Func <object>)operation.Value
                           : () => operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value));

                    var wireUpAction = valueFunc == null
                        ? null
                        : new Action(() =>
                    {
                        var newPropertyValue = valueFunc();
                        if (CanAssignToProperty(newPropertyValue, operation.ColumnPropertyMetadata))
                        {
                            //  Here we're setting the foreign key pointing at the child object on the owner.
                            operation.ColumnPropertyMetadata.Prop.SetValue(operation.Owner, newPropertyValue);
                        }
                    });

                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        wireUpAction,
                        valueFunc);
                }
                else if (fkTargetColumn != null)
                {
                    var property = operation.ValueMetadata[fkTargetColumn];
                    if (property == null)
                    {
                        throw new ArgumentException(string.Format(
                                                        "Cannot UPDATE foreign key relationship for non existent target column '{0}'"
                                                        + " specified from column '{1}' on type '{2}'.",
                                                        fkTargetColumn,
                                                        operation.OwnerPropertyMetadata.ColumnName,
                                                        operation.ValueMetadata.DtoType));
                    }

                    var valueFunc = operation.Value == null
                        ? null
                        : (operation.Value is Func <object>
                           ?(Func <object>)operation.Value
                           : () => property.GetValue(operation.Value));

                    var wireUpAction = valueFunc == null
                        ? null
                        : new Action(() =>
                    {
                        var newPropertyValue = valueFunc();
                        if (CanAssignToProperty(newPropertyValue, operation.ColumnPropertyMetadata))
                        {
                            //  Here we're setting the foreign key pointing at the child's fkTargetColumn object on the owner.
                            operation.ColumnPropertyMetadata.Prop.SetValue(operation.Owner, newPropertyValue);
                        }
                    });

                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        wireUpAction,
                        valueFunc);
                }
                else
                {
                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        null,   //  Standard column value so no wire-up required
                        operation.Value);
                }
                ++count;
            }

            script.Buffer.Append(string.Format(@"
WHERE [{0}] = ", command.PrimaryKeyColumn));
            FormatWithParameter(script, @"{0};
",
                                ref paramIndex,
                                null, //  No need to wire this up to anything else.
                                new Func <object>(() => command.PrimaryKeyAsObject));
        }
        private static void AppendStandardUpdateCommand(
            Script script,
            UpdateCommand command,
            ref int paramIndex)
        {
            script.Buffer.Append(string.Format(@"UPDATE {0}
SET ", command.TableName));

            int count = 0;
            foreach (var operation in command.Operations)
            {
                if (count > 0)
                {
                    script.Buffer.Append(@",
    ");
                }
                script.Buffer.Append(string.Format(@"[{0}] = ", operation.ColumnName));
                bool useKey = false;
                string fkTargetColumn = null;

                if (null != operation.ValueMetadata)
                {
                    if ((null != operation.OwnerPropertyMetadata
                     && (operation.OwnerPropertyMetadata.HasAttribute<OneToOneAttribute>()
                         || operation.OwnerPropertyMetadata.HasAttribute<ManyToOneAttribute>()
                         || operation.OwnerPropertyMetadata.HasAttribute<ReferenceDataAttribute>())))
                    {
                        var manyToOne = operation.OwnerPropertyMetadata.GetAttribute<ManyToOneAttribute>();
                        if (manyToOne != null && manyToOne.ForeignKeyTargetColumnName != null)
                        {
                            fkTargetColumn = manyToOne.ForeignKeyTargetColumnName;
                        }
                        else
                        {
                            useKey = true;
                        }
                    }
                    else if (operation.ValueMetadata.HasAttribute<ReferenceDataAttribute>())
                    {
                        useKey = true;
                    }
                }

                if (useKey)
                {
                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        operation.Value == null
                            ? (object) DBNull.Value
                            : new Func<object>(() => operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
                }
                else if (fkTargetColumn != null)
                {

                    var property = operation.ValueMetadata[fkTargetColumn];
                    if (property == null)
                    {
                        throw new ArgumentException(string.Format(
                            "Cannot UPDATE foreign key relationship for non existent target column '{0}'"
                            + " specified from column '{1}' on type '{2}'.",
                            fkTargetColumn,
                            operation.OwnerPropertyMetadata.ColumnName,
                            operation.ValueMetadata.DtoType));
                    }

                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        operation.Value == null
                            ? (object) DBNull.Value
                            : new Func<object>(() => property.GetValue(operation.Value)));
                }
                else
                {
                    FormatWithParameter(script, "{0}", ref paramIndex, operation.Value);
                }
                ++count;
            }

            script.Buffer.Append(string.Format(@"
WHERE [{0}] = ", command.PrimaryKeyColumn));
            FormatWithParameter(script, @"{0};
", ref paramIndex, new Func<object>(() => command.PrimaryKeyAsObject));
        }
 private void ApplyUpdatesSoFarAsNewCommand(
     IList<BaseCommand> results,
     IList<UpdateOperation> updates,
     ref string updateTableName,
     ref object updatePk)
 {
     if (updates.Count == 0)
     {
         return;
     }
     var command = new UpdateCommand();
     updates.ForEach(update => command.AddOperation(update));
     results.Add(command);
     updates.Clear();
     updateTableName = null;
     updatePk = null;
 }
        private void AppendPropertyToInsertStatement(
            Script script,
            StringBuilder colBuff,
            StringBuilder valBuff,
            PropertyMetadata property,
            ref int index,
            BaseInsertDeleteOperation operation,
            ArrayList values,
            MethodInfo getter,
            UpdateCommand updateCommand)
        {
            object columnValueForUpdate = null;

            if (property.HasAttribute<ForeignKeyReferenceAttribute>()
                && null != operation.OwnerMetadata
                && _dtoMetadataCache.GetValidatedMetadataFor(
                    property.GetAttribute<ForeignKeyReferenceAttribute>().ReferencedDto).TableName ==
                operation.OwnerMetadata.TableName)
            {
                columnValueForUpdate = new Func<object>(() => operation.OwnerPrimaryKeyAsObject);
                values.Add(columnValueForUpdate);
            }
            else if (property.HasAttribute<ManyToOneAttribute>() && property.GetAttribute<ManyToOneAttribute>().ForeignKeyTargetColumnName != null)
            {
                var propValue = property.GetValue(operation.Value);
                var propTypeMetadata = _dtoMetadataCache.GetValidatedMetadataFor(property.Prop.PropertyType);
                if (null != propValue && null != propTypeMetadata)
                {
                    var targetName = property.GetAttribute<ManyToOneAttribute>().ForeignKeyTargetColumnName;
                    var fkTargetProperty = propTypeMetadata[targetName];
                    if (fkTargetProperty == null)
                    {
                        throw new ArgumentException(string.Format(
                            "Cannot INSERT foreign key value for non existent target column '{0}'"
                            + " specified from column '{1}'.",
                            targetName,
                            property.ColumnName));
                    }

                    columnValueForUpdate = new Func<object>(() => fkTargetProperty.GetValue(propValue));
                    values.Add(columnValueForUpdate);
                }
                else
                {
                    columnValueForUpdate = new Func<object>(() => null);
                    values.Add(columnValueForUpdate);
                }
            }
            else if (property.HasAttribute<ManyToOneAttribute>() || property.HasAttribute<OneToOneAttribute>())
            {
                if (property.HasAttribute<OneToOneAttribute>() && !property.HasAttribute<ForeignKeyReferenceAttribute>())
                {
                    //  One to one relationship where child table references parent rather than the other way around.
                    //  This will be saved along with the child object.
                    return;
                }

                object propValue = property.GetValue(operation.Value);
                DtoMetadata propMetadata = _dtoMetadataCache.GetValidatedMetadataFor(property.Prop.PropertyType);
                columnValueForUpdate = new Func<object>(
                        () =>
                            propValue == null || propMetadata == null
                                ? null
                                : propMetadata.GetPrimaryKeyValueAsObject(propValue));
                values.Add(columnValueForUpdate);
            }
            else
            {
                columnValueForUpdate = getter.Invoke(operation.Value, new object[0]);
                values.Add(columnValueForUpdate);
            }

            if (columnValueForUpdate is Func<object>)
            {
                script.WireUpActions.Add(() =>
                {
                    object newPropertyValue = ((Func<object>) columnValueForUpdate)();
                    if (CanAssignToProperty(newPropertyValue, property))
                    {
                        property.Prop.SetValue(operation.Value, newPropertyValue);
                    }
                });
            }

            updateCommand.AddOperation(new UpdateOperation()
            {
                ColumnPropertyMetadata = property,
                TableName = operation.ValueMetadata.TableName,
                Value = columnValueForUpdate,
                ValueMetadata = property.IsString || property.IsNumericType || property.IsEnum || ! property.IsReferenceType
                    ? null
                    : _dtoMetadataCache.GetMetadataFor(property.Prop.PropertyType),
                Owner = operation.Value,
                OwnerMetadata = operation.ValueMetadata,
                OwnerPrimaryKeyColumn = operation.ValueMetadata.PrimaryKey.ColumnName,
                OwnerPropertyMetadata = property
            });

            if (colBuff.Length > 0) {
                colBuff.Append(@", ");
                valBuff.Append(@", ");
            }

            colBuff.Append("[" + property.ColumnName + "]");

            valBuff.Append("{");
            valBuff.Append(index);
            valBuff.Append("}");

            ++index;
        }
        private bool AppendInsertCommand(Script script, InsertCommand command, ref int paramIndex)
        {
            var isPkAssignedByRdbms = true;
            var needsUpdateContingency = false;
            PropertyMetadata guidPKColumn = null;
            var operation = command.Operation;
            if (operation.ValueMetadata != null) {
                if (null != operation.OwnerPropertyMetadata
                    && operation.OwnerPropertyMetadata.HasAttribute<ManyToManyAttribute>())
                {
                    InsertRecordInLinkTable(script, ref paramIndex, operation);
                    script.Buffer.Append(@"
SELECT SCOPE_IDENTITY();
");
                }
                else if (operation.OwnerPropertyMetadata == null
                    || ((operation.OwnerPropertyMetadata.HasAttribute<OneToManyAttribute>()
                        || operation.OwnerPropertyMetadata.HasAttribute<OneToOneAttribute>())//IsOneToOneRelationshipWithFkOnParent(operation)) //  Because 1:1 with FK on child is like 1:N, and we already handle 1:1 with FK on parent anyway
                        && !operation.ValueMetadata.HasAttribute<ReferenceDataAttribute>()
                        && !operation.OwnerPropertyMetadata.HasAttribute<ReferenceDataAttribute>())) 
                {
                    //  INSERT the value into the table defined by ValueMetadata
                    
                    var colBuff = new StringBuilder();
                    var valBuff = new StringBuilder();
                    var values = new ArrayList();
                    var index = 0;

                    var updateCommand = new UpdateCommand();
                    var hasPrimaryKeyValueAlready = false;
                    string pkColumnName = null;

                    var columnsSeenBefore = CreateColumnsSeenBefore();
                    foreach (var property in operation.ValueMetadata.WriteableProperties)
                    {
                        if (property.IsPrimaryKey)
                        {
                            isPkAssignedByRdbms = !property.GetAttribute<PrimaryKeyAttribute>().IsUserAssigned;
                            pkColumnName = property.ColumnName;

                            var pkValue = operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value);
                            if (pkValue != null)
                            {
                                hasPrimaryKeyValueAlready = true;
                            }

                            //updateCommand.AddOperation(new UpdateOperation()
                            //{
                            //    ColumnName = property.ColumnName,
                            //    TableName = operation.ValueMetadata.TableName,
                            //    Value = pkValue,
                            //    ValueMetadata = _dtoMetadataCache.GetMetadataFor(property.Prop.PropertyType),
                            //    Owner = operation.Owner,
                            //    OwnerMetadata = operation.OwnerMetadata,
                            //    OwnerPrimaryKeyColumn = operation.ValueMetadata.PrimaryKey.ColumnName,
                            //    OwnerPropertyMetadata = property
                            //});

                            if (isPkAssignedByRdbms)
                            {
                                var type = property.Prop.PropertyType;
                                if (type != typeof (int)
                                    && type != typeof (int?)
                                    && type != typeof (long)
                                    && type != typeof (long?))
                                {
                                    if (property.Prop.PropertyType == typeof (Guid?)
                                        || property.Prop.PropertyType == typeof (Guid))
                                    {
                                        guidPKColumn = property;
                                    }
                                    else
                                    {
                                        throw new ArgumentException(string.Format(
                                            "Unsupported primary key type {0} on entity {1}. Primary keys must be nullable ints, longs or GUIDs.",
                                            type.FullName,
                                            operation.ValueMetadata.DtoType.FullName));
                                    }
                                }

                                continue;
                            }
                        }

                        var getter = property.Prop.GetGetMethod();

                        if (getter == null
                            || property.HasAttribute<ManyToManyAttribute>()
                            || property.HasAttribute<OneToManyAttribute>()
                            || ! property.IsSaveable)
                        {
                            continue;
                        }

                        //updateCommand.AddOperation(new UpdateOperation()
                        //{
                        //    ColumnName = property.ColumnName,
                        //    TableName = operation.ValueMetadata.TableName,
                        //    Value = operation.Value,
                        //    ValueMetadata = operation.ValueMetadata,
                        //    Owner = operation.Owner,
                        //    OwnerMetadata = operation.OwnerMetadata,
                        //    OwnerPrimaryKeyColumn = operation.OwnerPrimaryKeyColumn,
                        //    OwnerPropertyMetadata = operation.OwnerPropertyMetadata
                        //});

                        CheckAndThrowIfThisColumnHasBeenSeenBefore(
                            columnsSeenBefore, operation.ValueMetadata.TableName, property.ColumnName, command);

                        AppendPropertyToInsertStatement(
                            script, colBuff, valBuff, property, ref index, operation, values, getter, updateCommand);
                    }

                    needsUpdateContingency = !isPkAssignedByRdbms || hasPrimaryKeyValueAlready;

                    if (needsUpdateContingency)
                    {
                        script.Buffer.Append(
                            string.Format(@"IF EXISTS (SELECT * FROM {0} WHERE [{1}] = ",
                            operation.ValueMetadata.TableName,
                            pkColumnName));

                        //  N.B. The PK value doesn't need referencing with a function here because
                        //  we already have a primary key value.
                        FormatWithParameter(
                            script,
                            "{0}",
                            ref paramIndex,
                            null, //  No wire-up required for the IF EXISTS value
                            operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value));

                        script.Buffer.Append(@")
BEGIN
");
                        AppendUpdateCommand(script, updateCommand, ref paramIndex);

                        if (guidPKColumn == null)
                        {
                            script.Buffer.Append(string.Format(@"
SELECT {0};
", operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
                        }
                        else
                        {
                            script.Buffer.Append(string.Format(@"
SELECT '{0}';
", operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value)));
                        }

                        script.Buffer.Append(@"
END
ELSE
BEGIN
");
                    }

                    script.Buffer.Append(string.Format(
                    @"INSERT INTO {0} (
    {1}
)",
                        operation.ValueMetadata.TableName,
                        colBuff));

                    if (guidPKColumn != null && isPkAssignedByRdbms)
                    {
                        script.Buffer.Append(string.Format(@"
OUTPUT inserted.[{0}]
",
                            guidPKColumn.ColumnName));
                    }

                    script.Buffer.Append(@" VALUES (
    ");

                    FormatWithParameter(script, valBuff.ToString(), ref paramIndex, null, values.ToArray());
                    script.Buffer.Append(@"
);
");
                    script.InsertedValue = operation.Value;
                    script.InsertedValueMetadata = operation.ValueMetadata;

                    if (guidPKColumn == null && isPkAssignedByRdbms)
                    {
                        script.Buffer.Append(@"
SELECT SCOPE_IDENTITY();
");
                    }

                    if (needsUpdateContingency)
                    {
                        script.Buffer.Append(@"
END
");
                    }
                }
            }
            else
            {
                throw new ArgumentException(
                    string.Format(
                        "Invalid INSERT command: {0}",
                        JsonConvert.SerializeObject(command)),
                    "command");
            }
            return isPkAssignedByRdbms;
        }
        private static void AppendStandardUpdateCommand(
            Script script,
            UpdateCommand command,
            ref int paramIndex)
        {
            script.Buffer.Append(string.Format(@"UPDATE {0}
SET ", command.TableName));

            int count = 0;
            var columnsSeenBefore = CreateColumnsSeenBefore();
            foreach (var operation in command.Operations)
            {
                if (count > 0)
                {
                    script.Buffer.Append(@",
    ");
                }

                CheckAndThrowIfThisColumnHasBeenSeenBefore(
                    columnsSeenBefore, command.TableName, operation.ColumnName, command);

                script.Buffer.Append(string.Format(@"[{0}] = ", operation.ColumnName));
                bool useKey = false;
                string fkTargetColumn = null;

                if (null != operation.ValueMetadata)
                {
                    if ((null != operation.OwnerPropertyMetadata
                     && (operation.OwnerPropertyMetadata.HasAttribute<OneToOneAttribute>()
                         || operation.OwnerPropertyMetadata.HasAttribute<ManyToOneAttribute>()
                         || operation.OwnerPropertyMetadata.HasAttribute<ReferenceDataAttribute>())))
                    {
                        var manyToOne = operation.OwnerPropertyMetadata.GetAttribute<ManyToOneAttribute>();
                        if (manyToOne != null && manyToOne.ForeignKeyTargetColumnName != null)
                        {
                            fkTargetColumn = manyToOne.ForeignKeyTargetColumnName;
                        }
                        else
                        {
                            useKey = true;
                        }
                    }
                    else if (operation.ValueMetadata.HasAttribute<ReferenceDataAttribute>())
                    {
                        useKey = true;
                    }
                }

                if (useKey)
                {
                    var valueFunc = operation.Value == null
                        ? null
                        : (operation.Value is Func<object>
                            ? (Func<object>) operation.Value
                            : () => operation.ValueMetadata.GetPrimaryKeyValueAsObject(operation.Value));

                    var wireUpAction = valueFunc == null
                        ? null
                        : new Action(() =>
                        {
                            var newPropertyValue = valueFunc();
                            if (CanAssignToProperty(newPropertyValue, operation.ColumnPropertyMetadata))
                            {
                                //  Here we're setting the foreign key pointing at the child object on the owner.
                                operation.ColumnPropertyMetadata.Prop.SetValue(operation.Owner, newPropertyValue);
                            }
                        });

                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        wireUpAction,
                        valueFunc);
                }
                else if (fkTargetColumn != null)
                {

                    var property = operation.ValueMetadata[fkTargetColumn];
                    if (property == null)
                    {
                        throw new ArgumentException(string.Format(
                            "Cannot UPDATE foreign key relationship for non existent target column '{0}'"
                            + " specified from column '{1}' on type '{2}'.",
                            fkTargetColumn,
                            operation.OwnerPropertyMetadata.ColumnName,
                            operation.ValueMetadata.DtoType));
                    }

                    var valueFunc = operation.Value == null
                        ? null
                        : (operation.Value is Func<object>
                            ? (Func<object>) operation.Value
                            : () => property.GetValue(operation.Value));

                    var wireUpAction = valueFunc == null
                        ? null
                        : new Action(() =>
                        {
                            var newPropertyValue = valueFunc();
                            if (CanAssignToProperty(newPropertyValue, operation.ColumnPropertyMetadata))
                            {
                                //  Here we're setting the foreign key pointing at the child's fkTargetColumn object on the owner.
                                operation.ColumnPropertyMetadata.Prop.SetValue(operation.Owner, newPropertyValue);
                            }
                        });

                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        wireUpAction,
                        valueFunc);
                }
                else
                {
                    FormatWithParameter(
                        script,
                        "{0}",
                        ref paramIndex,
                        null,   //  Standard column value so no wire-up required
                        operation.Value);
                }
                ++count;
            }

            script.Buffer.Append(string.Format(@"
WHERE [{0}] = ", command.PrimaryKeyColumn));
            FormatWithParameter(script, @"{0};
",
                ref paramIndex,
                null,   //  No need to wire this up to anything else.
                new Func<object>(() => command.PrimaryKeyAsObject));
        }