public void BasicSyncTest() { // Arrange StringBuilder trace; var sqlMock = new Mock <ISql>(); sqlMock.Setup(m => m.GetAllTables()) .Returns(() => new List <(string fullName, int objectId)> { ("dbo.Dogs", 1), ("dbo.Cats", 2) }); sqlMock.Setup(m => m.GetStoredProcsThatRefEntity(It.IsAny <string>())) .Returns(() => new List <(string fullName, int objectId)> { ("dbo.GetDogsById", 3), ("dbo.DogsSave", 4), ("dbo.DogsUpdate", 5) }); sqlMock.Setup(m => m.GetColumnNames(It.IsAny <int>())) .Returns(() => new string[] { "id", "name", "thing" }); sqlMock.Setup(m => m.GetTableObjectId(It.IsAny <string>())) .Returns(() => new int?(1)); var sync = new HarpSynchronizer(sqlMock.Object, new StringBuilder()); var harpFile = new HarpFile(); harpFile.Entities.Add("Dogs", new Entity { Name = "Dogs", Table = string.Empty, // blank Properties = new Dictionary <string, string> { { "ID", string.Empty }, // blank { "Name", string.Empty } // blank }, Behaviors = new Dictionary <string, string> { { "Get by id", string.Empty } // blank } }); // Act var results = sync.Synchronize(harpFile); // Assert Assert.IsTrue(harpFile.IsFullyMapped); }
static void Main(string[] args) { var trace = new StringBuilder(); try { var harpFilePath = Path.Combine(Environment.CurrentDirectory, "DataLayer.harp"); Console.WriteLine(harpFilePath); // read var inYaml = File.ReadAllText(harpFilePath); var result = HarpFile.FromYaml(inYaml); Console.WriteLine($"Parse: {result.code}"); if (result.code != HarpFile.ParseResult.OK) { return; } // synchronize var sync = new HarpSynchronizer(new Sql(), trace); var sResult = sync.Synchronize(result.file); Console.WriteLine($"Sync: {sResult.Code}"); if (sResult.Code != HarpSynchronizer.SynchronizeResultCode.OK) { return; } if (sResult.WasUpdated) { var outYaml = result.file.GenerateYaml(); File.WriteAllText(harpFilePath, outYaml); } } finally { Console.WriteLine($"----- TRACE -----"); Console.WriteLine(trace.ToString()); Console.WriteLine($"-----------------"); Console.ReadLine(); } }
HarpFile generateSampleHarpFile() { var file = new HarpFile { Config = new HarpFile.HarpConfig { OutputDirectory = "asdf", SqlConnectionString = "asdf;" }, Entities = new Dictionary <string, Entity> { { "Dogs", new Entity { Name = "Thing", Table = "dbo.Things", Properties = new Dictionary <string, string> { { "PropA", "prop_a" } }, Behaviors = new Dictionary <string, string> { { "Do thing A", "dbo.DoThingA" } } } }, { "Cats", new Entity { Name = "Thing2", Table = "dbo.Things2", Properties = new Dictionary <string, string> { { "PropA2", "prop_a2" } }, Behaviors = new Dictionary <string, string> { { "Do thing A2", "dbo.DoThingA2" } } } }, } }; return(file); }
public SyncResults Synchronize(HarpFile mapFile) { var results = new SyncResults(); var isValid = sql.ConfigureAndTest(mapFile.Config.SqlConnectionString); if (!isValid) { results.Code = SynchronizeResultCode.InvalidSqlConnectionString; return(results); } try { foreach (var entry in mapFile.Entities) { int?tableId; var entity = entry.Value; // Table if (string.IsNullOrWhiteSpace(entity.Table)) { // Try match to an existing table var tables = sql.GetAllTables(); var matches = tables.Where( t => StringMatcher.IsAFuzzyMatch( getObjectName(t.fullName), entity.Name )); if (matches.Count() > 1) { // Error: too many matches, can't decide. results.Code = SynchronizeResultCode.EntityNameMatchesTooManyTables; return(results); } else if (matches.Count() == 0) { // Error: no matches results.Code = SynchronizeResultCode.EntityNameMatchesNoTables; return(results); } var tableMatch = matches.Single(); // Capture table name on entity entity.Table = tableMatch.fullName; results.WasUpdated = true; trace.AppendLine($"Table match: {entity.Table}"); } tableId = sql.GetTableObjectId(entity.Table); if (tableId == null) { results.Code = SynchronizeResultCode.MatchedTableDoesNotExist; return(results); } var columnsForEntity = sql.GetColumnNames(tableId.Value); // Columns, unmapped if (entity.Properties.Any(p => string.IsNullOrWhiteSpace(p.Value))) { for (int x = 0; x < entity.Properties.Count; x++) { var propEntry = entity.Properties.ElementAt(x); // Excludes any already mapped if (propEntry.Value != null) { continue; } var prop = propEntry.Value; var availableMatches = columnsForEntity.Where(c => !entity.Properties.Any(p => p.Value == c)); var matches = availableMatches.Where(c => StringMatcher.IsAFuzzyMatch(c, propEntry.Key)); if (matches.Any()) { var match = matches.First(); entity.Properties[propEntry.Key] = match; results.WasUpdated = true; trace.AppendLine($"Property match: {propEntry.Key} = {match}"); } else { // Error: No matches for column results.Code = SynchronizeResultCode.ColumnMatchingError; return(results); } } // Track the unmapped var unmappedCols = columnsForEntity.Except(entity.Properties.Select(p => p.Value)); results.UnmappedTableColumns.AddRange(unmappedCols); } else if (entity.Properties.Count() == 0) { // Autopopulate property list with all available columns foreach (var column in columnsForEntity) { var humanName = column.Humanize(); entity.AddProperty(humanName, column); results.WasUpdated = true; trace.AppendLine($"Property match: {humanName} = {column}"); } } // Columns, mapped var allExistingMappedColumnsExist = entity.Properties.All(p => columnsForEntity.Any(c => string.Equals(c, p.Value, StringComparison.OrdinalIgnoreCase))); if (!allExistingMappedColumnsExist) { results.Code = SynchronizeResultCode.MatchedColumnDoesNotExist; return(results); } var procsForEntity = sql.GetStoredProcsThatRefEntity(entity.Table); // Behaviours, unmapped if (entity.Behaviors.Any(b => string.IsNullOrWhiteSpace(b.Value))) { for (var x = 0; x < entity.Behaviors.Count; x++) { var behaveEntry = entity.Behaviors.ElementAt(x); // ignore already mapped behaviors if (behaveEntry.Value != null) { continue; } var behavior = behaveEntry.Value; // Excludes any already mapped var availableMatches = procsForEntity.Where(p => !entity.Behaviors.Any(b => b.Value == p.fullName)); var matches = availableMatches.Select(p => new ProcName(p.fullName)) .OrderByDescending(p => { // Remove entity name from proc's name, to make it more likely to get matched // since there's less character changes required for a total match. // e.g. 1 = get dogs by id = get by id (becomes the most closest match) // 2 = get cats by id = get cats by id var processed = p.HumanizedName.Replace((" " + entity.Name + " "), string.Empty); return(stringCompareScore(behaveEntry.Key, processed)); }).ToArray(); if (matches.Any()) { var match = matches.First().FullName; entity.Behaviors[behaveEntry.Key] = match; results.WasUpdated = true; trace.AppendLine($"Behavior match: {behaveEntry.Key} = {match}"); } } // Track the unmapped var unmappedCols = procsForEntity.Select(p => p.fullName) .Except(entity.Behaviors.Select(b => b.Value)); results.UnmappedStoredProcs.AddRange(unmappedCols); // Ensure all stored procs that have been matched // (either from this process or manually) exist. var allExistingMappedProcsExist = entity.Behaviors.All(b => procsForEntity.Any(pr => string.Equals(pr.fullName, b.Value, StringComparison.OrdinalIgnoreCase))); if (!allExistingMappedProcsExist) { results.Code = SynchronizeResultCode.MatchedProcDoesNotExist; return(results); } } else if (entity.Behaviors.Count() == 0) { // Autopopulate behavior list with all available procs foreach (var proc in procsForEntity) { var humanName = removeWord(entity.Name, getObjectName(proc.fullName).Humanize()).Humanize(); if (string.IsNullOrWhiteSpace(humanName)) { results.Code = SynchronizeResultCode.ProcMatchingError; return(results); } entity.AddBehavior(humanName, proc.fullName); results.WasUpdated = true; trace.AppendLine($"Behavior match: {humanName} = {proc}"); } } } // Validate foreach (var entity in mapFile.Entities.Select(e => e.Value)) { if (!entity.Behaviors.Any()) { results.Code = SynchronizeResultCode.AtLeastOneBehaviorRequired; return(results); } } results.Code = mapFile.Entities.All(e => e.Value.IsFullyMapped) ? SynchronizeResultCode.OK : SynchronizeResultCode.NotAllMapped; } catch (Exception ex) { trace.AppendLine($"{ex.GetType().Name}: {ex.ToString()}"); results.Code = SynchronizeResultCode.UnknownError; } return(results); }
static void Main(string[] args) { if (args == null || args.Count() < 2) { logAndTerminate("Both parameters must be specified: {command} {file}"); return; } var cmd = args[0]; var file = args[1]; if (string.IsNullOrWhiteSpace(cmd) || string.IsNullOrWhiteSpace(file)) { logAndTerminate("Both parameters must be specified: {command} {file}"); return; } var actionSync = "sync"; var actionSyncAndGenerate = "syncgen"; var supportedActions = new[] { actionSync, actionSyncAndGenerate }; if (!supportedActions.Any(a => cmd.ToLower() == a.ToLower())) { logAndTerminate("Command not recognized."); return; } if (!File.Exists(file)) { file = Path.Combine(Environment.CurrentDirectory, file); if (!File.Exists(file)) { logAndTerminate("Could not find the specified file"); return; } } if (file.EndsWith(".harp")) { logAndTerminate("File is not a .harp file"); return; } var verboseOutput = (args.Length > 2); var sb = new StringBuilder(); var sync = new HarpSynchronizer(new Sql(), sb); // read file var harpYaml = File.ReadAllText(file); // load var loadResult = HarpFile.FromYaml(harpYaml); if (loadResult.code != HarpFile.ParseResult.OK) { logAndTerminate($"Could not load harp file: {loadResult.code}"); return; } var syncSuccessful = false; var syncResult = sync.Synchronize(loadResult.file); if (syncResult.Code == HarpSynchronizer.SynchronizeResultCode.OK) { syncSuccessful = true; log("Sync was successful"); if (syncResult.WasUpdated) { log("Harp file was updated"); } if (syncResult.UnmappedStoredProcs.Any()) { log("There are unmapped stored procedures"); } if (syncResult.UnmappedTableColumns.Any()) { log("There are unmapped table columns"); } } else { log($"Sync was NOT successful: {syncResult.Code}"); if (syncResult.UnmappedStoredProcs.Any()) { log("There are also some unmapped stored procedures"); } if (syncResult.UnmappedTableColumns.Any()) { log("There are also some unmapped table columns"); } Environment.Exit(1); } if (cmd.ToLower() == actionSyncAndGenerate && syncSuccessful) { log("Not implemented"); Environment.Exit(1); //var gen = new HarpGenerator(); //gen.Generate(HarpFile,) } }
public GenerateResults Generate(HarpFile mapFile, out StringBuilder trace) { trace = new StringBuilder(); return(new GenerateResults(GenerateResultCode.UnknownError)); }