diff --git a/AsPartitionProcessing/AsPartitionProcessing.SampleClient/Program.cs b/AsPartitionProcessing/AsPartitionProcessing.SampleClient/Program.cs index 20dc2b8..dbc434d 100644 --- a/AsPartitionProcessing/AsPartitionProcessing.SampleClient/Program.cs +++ b/AsPartitionProcessing/AsPartitionProcessing.SampleClient/Program.cs @@ -9,13 +9,14 @@ namespace AsPartitionProcessing.SampleClient { InitializeInline, InitializeFromDatabase, - MergePartitions + MergePartitions, + DefragPartitionedTables } class Program { //Set sample execution mode here: - const SampleExecutionMode ExecutionMode = SampleExecutionMode.InitializeInline; + const SampleExecutionMode ExecutionMode = SampleExecutionMode.InitializeInline; static void Main(string[] args) { @@ -46,6 +47,10 @@ namespace AsPartitionProcessing.SampleClient { PartitionProcessor.MergePartitions(modelConfig, LogMessage, "Internet Sales", Granularity.Yearly, "2012"); } + else if (ExecutionMode == SampleExecutionMode.DefragPartitionedTables) + { + PartitionProcessor.DefragPartitionedTables(modelConfig, LogMessage); + } else { PartitionProcessor.PerformProcessing(modelConfig, LogMessage); @@ -57,8 +62,10 @@ namespace AsPartitionProcessing.SampleClient Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(); Console.WriteLine(exc.Message, null); + Console.WriteLine(); } + Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Press any key to exit."); Console.ReadKey(); } @@ -164,7 +171,10 @@ namespace AsPartitionProcessing.SampleClient } catch (Exception exc) { + Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(exc.Message); + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Press any key to exit."); Console.ReadKey(); Environment.Exit(0); //Avoid recursion if errored connecting to db diff --git a/AsPartitionProcessing/AsPartitionProcessing/ConfigDatabaseHelper.cs b/AsPartitionProcessing/AsPartitionProcessing/ConfigDatabaseHelper.cs index c673ea4..961633d 100644 --- a/AsPartitionProcessing/AsPartitionProcessing/ConfigDatabaseHelper.cs +++ b/AsPartitionProcessing/AsPartitionProcessing/ConfigDatabaseHelper.cs @@ -52,13 +52,12 @@ namespace AsPartitionProcessing List modelConfigs = new List(); ModelConfiguration modelConfig = null; int currentModelConfigurationID = -1; + TableConfiguration tableConfig = null; + int currentTableConfigurationID = -1; SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { - TableConfiguration tableConfig = null; - int currentTableConfigurationID = -1; - if (modelConfig == null || currentModelConfigurationID != Convert.ToInt32(reader["ModelConfigurationID"])) { modelConfig = new ModelConfiguration(); diff --git a/AsPartitionProcessing/AsPartitionProcessing/PartitionProcessor.cs b/AsPartitionProcessing/AsPartitionProcessing/PartitionProcessor.cs index 35e0671..d75b57f 100644 --- a/AsPartitionProcessing/AsPartitionProcessing/PartitionProcessor.cs +++ b/AsPartitionProcessing/AsPartitionProcessing/PartitionProcessor.cs @@ -86,13 +86,17 @@ namespace AsPartitionProcessing } else { - //Partitioned table. Process based on partitioning configuration(s). + //Validate multiple granularity ranges. + tableConfiguration.ValidatePartitioningConfigurations(); + + //Find template partition. Partition templatePartition = table.Partitions.Find(tableConfiguration.AnalysisServicesTable); if (templatePartition == null) { throw new InvalidOperationException($"Table {tableConfiguration.AnalysisServicesTable} does not contain a partition with the same name to act as the template partition."); } + //Process based on partitioning configuration(s). foreach (PartitioningConfiguration partitioningConfiguration in tableConfiguration.PartitioningConfigurations) { LogMessage("", false); @@ -207,6 +211,7 @@ namespace AsPartitionProcessing { LogMessage($"Inner exception message: {exc.InnerException.Message}", false); } + LogMessage("", false); } finally { @@ -350,6 +355,79 @@ namespace AsPartitionProcessing } } + /// + /// Defragment all partitions tables in a tabular model based on configuration + /// + /// Configuration info for the model + /// Pointer to logging method + public static void DefragPartitionedTables(ModelConfiguration modelConfiguration, LogMessageDelegate messageLogger) + { + _modelConfiguration = modelConfiguration; + _messageLogger = messageLogger; + + Server server = new Server(); + try + { + Database database; + Connect(server, out database); + + Console.ForegroundColor = ConsoleColor.White; + LogMessage($"Start: {DateTime.Now.ToString("hh:mm:ss tt")}", false); + LogMessage($"Server: {_modelConfiguration.AnalysisServicesServer}", false); + LogMessage($"Database: {_modelConfiguration.AnalysisServicesDatabase}", false); + Console.ForegroundColor = ConsoleColor.Yellow; + + LogMessage("", false); + LogMessage($"Defrag partitioned tables in database {_modelConfiguration.AnalysisServicesDatabase}", false); + LogMessage(new String('-', _modelConfiguration.AnalysisServicesDatabase.Length + 38), false); + LogMessage("", false); + LogMessage("=>Actions & progress:", false); + + foreach (TableConfiguration tableConfiguration in _modelConfiguration.TableConfigurations) + { + //Only interested in partitoned tables + if (tableConfiguration.PartitioningConfigurations.Count > 0) + { + Table table = database.Model.Tables.Find(tableConfiguration.AnalysisServicesTable); + if (table == null) + { + throw new Microsoft.AnalysisServices.ConnectionException($"Could not connect to table {tableConfiguration.AnalysisServicesTable}."); + } + + LogMessage($"Defrag table {tableConfiguration.AnalysisServicesTable} ...", true); + table.RequestRefresh(RefreshType.Defragment); + database.Model.SaveChanges(); + } + } + + Console.ForegroundColor = ConsoleColor.White; + LogMessage("", false); + LogMessage("Finish: " + DateTime.Now.ToString("hh:mm:ss tt"), false); + } + catch (Exception exc) + { + Console.ForegroundColor = ConsoleColor.Red; + LogMessage("", false); + LogMessage($"Exception occurred: {DateTime.Now.ToString("hh:mm:ss tt")}", false); + LogMessage($"Exception message: {exc.Message}", false); + if (exc.InnerException != null) + { + LogMessage($"Inner exception message: {exc.InnerException.Message}", false); + } + LogMessage("", false); + } + finally + { + try + { + _modelConfiguration = null; + _messageLogger = null; + if (server != null) server.Disconnect(); + } + catch { } + } + } + #endregion #region Private Methods diff --git a/AsPartitionProcessing/AsPartitionProcessing/PartitioningConfiguration.cs b/AsPartitionProcessing/AsPartitionProcessing/PartitioningConfiguration.cs index 213696c..ff260ff 100644 --- a/AsPartitionProcessing/AsPartitionProcessing/PartitioningConfiguration.cs +++ b/AsPartitionProcessing/AsPartitionProcessing/PartitioningConfiguration.cs @@ -5,7 +5,7 @@ namespace AsPartitionProcessing /// /// Configuration information for partitioning of a table within an AS tabular model. /// - public class PartitioningConfiguration + public class PartitioningConfiguration : IComparable { /// /// ID of the PartitioningConfiguration table. @@ -101,8 +101,8 @@ namespace AsPartitionProcessing default: break; } - } + public int CompareTo(PartitioningConfiguration other) => string.Compare(this.LowerBoundary.ToString("yyyy-MM-dd"), other.LowerBoundary.ToString("yyyy-MM-dd")); } /// diff --git a/AsPartitionProcessing/AsPartitionProcessing/TableConfiguration.cs b/AsPartitionProcessing/AsPartitionProcessing/TableConfiguration.cs index 97ca5c8..0c80a6c 100644 --- a/AsPartitionProcessing/AsPartitionProcessing/TableConfiguration.cs +++ b/AsPartitionProcessing/AsPartitionProcessing/TableConfiguration.cs @@ -47,5 +47,77 @@ namespace AsPartitionProcessing public TableConfiguration() { } + + /// + /// Validate multiple granularities to ensure no overlapping ranges, etc. + /// + public void ValidatePartitioningConfigurations() + { + if (this.PartitioningConfigurations.Count > 1) + { + this.PartitioningConfigurations.Sort(); //Sorts by LowerBoundary value + DateTime previousUpperBoundary = DateTime.MinValue; + Granularity previousGranularity = Granularity.Daily; + bool foundDaily = false, foundMonthly = false, foundYearly = false; + + foreach (PartitioningConfiguration partitioningConfiguration in this.PartitioningConfigurations) + { + #region Check don't have multiple partitioning configurations with same granularity + + string multiSameGrainErrorMessage = $"Table {this.AnalysisServicesTable} contains multiple {{0}} partitioning configurations, which is not allowed."; + switch (partitioningConfiguration.Granularity) + { + case Granularity.Daily: + if (foundDaily) + { + throw new InvalidOperationException(string.Format(multiSameGrainErrorMessage, "daily")); + } + else + { + foundDaily = true; + } + break; + case Granularity.Monthly: + if (foundMonthly) + { + throw new InvalidOperationException(string.Format(multiSameGrainErrorMessage, "monthly")); + } + else + { + foundMonthly = true; + } + break; + case Granularity.Yearly: + if (foundYearly) + { + throw new InvalidOperationException(string.Format(multiSameGrainErrorMessage, "yearly")); + } + else + { + foundYearly = true; + } + break; + default: + break; + } + + #endregion + + #region Check don't have overlapping date ranges + + if (partitioningConfiguration.LowerBoundary <= previousUpperBoundary) + { + throw new InvalidOperationException($"Table {this.AnalysisServicesTable} contains partitioning configurations with overlapping date ranges, which is not allowed. {previousGranularity.ToString()} upper boundary is {previousUpperBoundary.ToString("yyyy-MM-dd")}; {partitioningConfiguration.Granularity.ToString()} lower boundary is {partitioningConfiguration.LowerBoundary.ToString("yyyy-MM-dd")}."); + } + else + { + previousUpperBoundary = partitioningConfiguration.UpperBoundary; + previousGranularity = partitioningConfiguration.Granularity; + } + + #endregion + } + } + } } }