Skip to content

Instantly share code, notes, and snippets.

@odytrice
Created November 2, 2016 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save odytrice/4067d4d35497fb51bec1c16a59151360 to your computer and use it in GitHub Desktop.
Save odytrice/4067d4d35497fb51bec1c16a59151360 to your computer and use it in GitHub Desktop.
Merge Command For Fluent Migrator written in F#
namespace FluentMigrator.Extensions
open FluentMigrator.Expressions
open FluentMigrator.Infrastructure
open System
open System.Collections.Generic
open System.Linq
open FluentMigrator
open FluentMigrator.Model
open System.Data
open System.ComponentModel
type IMergeDataOrMatchSyntax<'T> =
abstract member Row : 'T -> IMergeDataOrMatchSyntax<'T>
abstract member Match: ('T -> 'M) -> unit;
type IMergeDataSyntax =
abstract member Row: 'T -> IMergeDataOrMatchSyntax<'T>
and IMergeDataOrInSchemaSyntax =
inherit IMergeDataSyntax
abstract member InSchema: string -> IMergeDataSyntax;
type IMergeExpressionRoot =
abstract member IntoTable : string -> IMergeDataOrInSchemaSyntax
type MergeDataExpression() =
inherit MigrationExpressionBase()
let rows = ResizeArray<InsertionDataDefinition>();
let matchColumns = ResizeArray<string>();
let additionalFeatures = new Dictionary<string, obj>();
member val SchemaName = Unchecked.defaultof<string> with get,set
member val TableName = Unchecked.defaultof<string> with get,set
member this.Rows with get() = rows
member this.AdditionalFeatures with get() = additionalFeatures
member this.MatchColumns with get() = matchColumns
override this.CollectValidationErrors (errors: ICollection<string>) = ()
override this.ExecuteWith (processor : IMigrationProcessor) =
let existingDataSet = processor.ReadTableData(this.SchemaName, this.TableName)
let existingTable = existingDataSet.Tables.[0]
for row in rows do
let anyColumn (r:DataRow) =
let select (mc:string) =
let ex = r.[mc]
let nw = row.Where(fun p -> p.Key = mc).Select(fun p -> p.Value).SingleOrDefault()
match ex, nw with
| null, _ -> ex = nw
| _ , null -> ex = nw
| _ -> ex.Equals(nw)
matchColumns.Select(select).All(fun m -> m);
let exists = existingTable.Rows.OfType<DataRow>().Any(Func<DataRow,bool> anyColumn)
if exists then
this.ExecuteUpdateWith(processor, row)
else
this.ExecuteInsertWith(processor, row)
member this.ExecuteUpdateWith (processor:IMigrationProcessor, row: ResizeArray<KeyValuePair<string, obj>>) =
let update = UpdateDataExpression(
SchemaName = this.SchemaName,
TableName = this.TableName,
IsAllRows = false,
Set = row.Where(fun p -> not(matchColumns.Contains(p.Key))).ToList(),
Where = matchColumns.Select(fun mc ->
let v = row.Where(fun p -> p.Key = mc).Select(fun p -> p.Value).SingleOrDefault()
new KeyValuePair<string, obj>(mc, v)
).ToList()
)
processor.Process(update);
member this.ExecuteInsertWith (processor:IMigrationProcessor, row:InsertionDataDefinition) =
let insert = InsertDataExpression(
SchemaName = this.SchemaName,
TableName = this.TableName)
for af in additionalFeatures do
insert.AdditionalFeatures.Add(af.Key, af.Value)
insert.Rows.Add(row)
processor.Process(insert)
[<AbstractClass>]
type MergeDataExpressionBuilderBase(expression: MergeDataExpression) =
member this.ExtractData(dataAsAnonymousType:obj): IDictionary<string, obj> =
let data = new Dictionary<string, obj>();
let properties = TypeDescriptor.GetProperties(dataAsAnonymousType);
for property in properties do
data.Add(property.Name, property.GetValue(dataAsAnonymousType))
data :> _
interface ISupportAdditionalFeatures with
member this.AddAdditionalFeature (feature: string, value: obj) =
if (expression.AdditionalFeatures.ContainsKey(feature) |> not) then
expression.AdditionalFeatures.Add(feature, value);
else
expression.AdditionalFeatures.[feature] <- value;
type MergeDataExpressionTypedBuilder<'T>(expression: MergeDataExpression) =
inherit MergeDataExpressionBuilderBase(expression)
interface IMergeDataOrMatchSyntax<'T> with
member this.Row (dataAsAnonymousType: 'T) :IMergeDataOrMatchSyntax<'T> =
let data = this.ExtractData(dataAsAnonymousType)
let dataDefinition = new InsertionDataDefinition()
dataDefinition.AddRange(data)
expression.Rows.Add(dataDefinition)
this :> _
member this.Match (f:'T -> 'M) =
let properties = TypeDescriptor.GetProperties(typeof<'M>)
for property in properties do
expression.MatchColumns.Add(property.Name)
type MergeDataExpressionStartBuilder(expression) =
inherit MergeDataExpressionBuilderBase(expression)
interface IMergeDataOrInSchemaSyntax with
member this.Row(dataAsAnonymousType: 'T) =
let typed = MergeDataExpressionTypedBuilder<'T>(expression) :> IMergeDataOrMatchSyntax<_>
typed.Row(dataAsAnonymousType)
member this.InSchema(schemaName) =
expression.SchemaName <- schemaName;
this :> _
type MergeExpressionRoot(context:IMigrationContext) =
interface IMergeExpressionRoot with
member this.IntoTable(tableName: string): IMergeDataOrInSchemaSyntax=
let expression = MergeDataExpression( TableName = tableName );
context.Expressions.Add(expression);
MergeDataExpressionStartBuilder(expression) :> _
[<AbstractClass>]
type MigrationExtension =
inherit Migration
member this.Merge =
let context =
this.GetType()
.GetField("_context", System.Reflection.BindingFlags.NonPublic ||| System.Reflection.BindingFlags.Instance)
.GetValue(this) :?> IMigrationContext
new MergeExpressionRoot(context)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment