Skip to content

Commit

Permalink
https://github.com/christiandelbianco/monitor-table-change-with-sqlta…
Browse files Browse the repository at this point in the history
…bledependency/issues/66
  • Loading branch information
Christian Del Bianco committed May 21, 2018
1 parent 7859d66 commit fb23925
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 144 deletions.
11 changes: 0 additions & 11 deletions Developments/TableDependency.SqlClient/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,6 @@
<configuration>

<connectionStrings>
<!--Local SQL Server 2016 -->
<add name="IntegratedSecurityConnectionString"
connectionString="data source=.;initial catalog=TableDependencyDB;integrated security=SSPI"
providerName="System.Data.SqlClient"/>
<add name="DbOwnerSqlServerConnectionString"
connectionString="data source=.;initial catalog=TableDependencyDB;integrated security=False; User ID=DbOwnerRole_User;Password=Casadolcecasa1"
providerName="System.Data.SqlClient"/>
<add name="UserNotDboConnectionString"
connectionString="data source=.;initial catalog=TableDependencyDB;integrated security=False; User ID=Test_User;Password=Casadolcecasa1"
providerName="System.Data.SqlClient"/>

<!--Remote SQL Server 2016 -->
<add name="SqlServer2008 Test_User"
connectionString="Data Source=DESKTOP-DFTT9LE\SQLSERVER2008;initial catalog=TableDependencyDB;User ID=Test_User;Password=Casadolcecasa1"
Expand Down
32 changes: 13 additions & 19 deletions Developments/TableDependency.SqlClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,28 @@ private static void Main()
Console.WriteLine("Copyright (c) 2015-2018 Christian Del Bianco.");
Console.WriteLine("All rights reserved." + Environment.NewLine);
Console.WriteLine("**********************************************************************************************");
Console.WriteLine("Application used for development [choose connection string]:");
Console.WriteLine(" F1: SQL Server Developer 2014 - (LOCAL HOST) integrated security");
Console.WriteLine(" F2: SQL Server Developer 2014 - (LOCAL HOST) user with DB Owner Role");
Console.WriteLine(" F3: SQL Server Developer 2014 - (LOCAL HOST) user not DBO");
Console.WriteLine(" F4: SQL Server Developer 2008 - (DESKTOP-DFTT9LE\\SQLSERVER2008) user sa");
Console.WriteLine(" F5: SQL Server Developer 2008 - (DESKTOP-DFTT9LE\\SQLSERVER2008) user Test_User");
Console.WriteLine(" ESC to exit");
Console.WriteLine("Choose connection string:");
Console.WriteLine(" - F4: SQL Server Developer 2008 - (DESKTOP-DFTT9LE\\SQLSERVER2008) user sa");
Console.WriteLine(" - F5: SQL Server Developer 2008 - (DESKTOP-DFTT9LE\\SQLSERVER2008) user Test_User");
Console.WriteLine(" - ESC to exit");
Console.WriteLine("**********************************************************************************************");

consoleKeyInfo = Console.ReadKey();
if (consoleKeyInfo.Key == ConsoleKey.Escape) Environment.Exit(0);

} while (
consoleKeyInfo.Key != ConsoleKey.F1 &&
consoleKeyInfo.Key != ConsoleKey.F2 &&
consoleKeyInfo.Key != ConsoleKey.F3 &&
consoleKeyInfo.Key != ConsoleKey.F4 &&
consoleKeyInfo.Key != ConsoleKey.F5);


if (consoleKeyInfo.Key == ConsoleKey.F1) connectionString = ConfigurationManager.ConnectionStrings["IntegratedSecurityConnectionString"].ConnectionString;
if (consoleKeyInfo.Key == ConsoleKey.F2) connectionString = ConfigurationManager.ConnectionStrings["DbOwnerSqlServerConnectionString"].ConnectionString;
if (consoleKeyInfo.Key == ConsoleKey.F3) connectionString = ConfigurationManager.ConnectionStrings["UserNotDboConnectionString"].ConnectionString;
} while (consoleKeyInfo.Key != ConsoleKey.F4 && consoleKeyInfo.Key != ConsoleKey.F5);

if (consoleKeyInfo.Key == ConsoleKey.F4) connectionString = ConfigurationManager.ConnectionStrings["SqlServer2008 sa"].ConnectionString;
if (consoleKeyInfo.Key == ConsoleKey.F5) connectionString = ConfigurationManager.ConnectionStrings["SqlServer2008 Test_User"].ConnectionString;

var mapper = new ModelToTableMapper<Customer>();
mapper.AddMapping(c => c.Id, "CustomerID");

using (var dep = new SqlTableDependency<Customer>(connectionString, "Customers", mapper: mapper, includeOldValues: true))
var updateOf = new UpdateOfModel<Customer>();
updateOf.Add(i => i.CompanyName);
updateOf.Add(i => i.ContactName);

using (var dep = new SqlTableDependency<Customer>(connectionString, "Customers", mapper: mapper, updateOf: updateOf, includeOldValues: true))
{
dep.OnChanged += Changed;
dep.OnError += OnError;
Expand All @@ -67,6 +59,8 @@ private static void Main()

private static void OnError(object sender, ErrorEventArgs e)
{
Console.Clear();

Console.WriteLine(e.Message);
Console.WriteLine(e.Error?.Message);
}
Expand Down
12 changes: 7 additions & 5 deletions TableDependency.SqlClient/Resources/SqlScriptsBroker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ DECLARE @records XML
DECLARE @theMessageContainer NVARCHAR(MAX)
DECLARE @dmlType NVARCHAR(10)
DECLARE @modifiedRecordsTable TABLE ([RowNumber] INT IDENTITY(1, 1), {2})
DECLARE @exceptTable TABLE ([RowNumber] INT, {17})
DECLARE @deletedTable TABLE ([RowNumber] INT IDENTITY(1, 1), {18})
DECLARE @insertedTable TABLE ([RowNumber] INT IDENTITY(1, 1), {18})
{5}
IF NOT EXISTS(SELECT * FROM INSERTED)
Expand Down Expand Up @@ -125,15 +128,15 @@ END CATCH

public const string InsertInTableVariableConsideringUpdateOf = @"IF ({0})
BEGIN
SET @dmlType = '{2}'
INSERT INTO @modifiedRecordsTable SELECT {1} FROM {3}
SET @dmlType = '{1}'
{2}
END
ELSE BEGIN
RETURN
END";

public const string InsertInTableVariable = @"SET @dmlType = '{1}'
INSERT INTO @modifiedRecordsTable SELECT {0} FROM {2}";
public const string InsertInTableVariable = @"SET @dmlType = '{0}'
{1}";

public const string ScriptDropAll = @"DECLARE @schema_id INT;
DECLARE @conversation_handle UNIQUEIDENTIFIER;
Expand Down Expand Up @@ -178,6 +181,5 @@ ELSE BEGIN
PRINT N'SqlTableDependency: Dropping activation procedure {0}_QueueActivationSender.';
IF EXISTS (SELECT * FROM sys.objects WITH (NOLOCK) WHERE schema_id = @schema_id AND name = N'{0}_QueueActivationSender') DROP PROCEDURE [{2}].[{0}_QueueActivationSender];";


}
}
100 changes: 58 additions & 42 deletions TableDependency.SqlClient/SqlTableDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ namespace TableDependency.SqlClient
public SqlTableDependency(
string connectionString,
string tableName = null,
string schemaName = null,
string schemaName = null,
IModelToTableMapper<T> mapper = null,
IUpdateOfModel<T> updateOf = null,
ITableDependencyFilter filter = null,
Expand Down Expand Up @@ -400,38 +400,28 @@ protected virtual string CreateWhereCondifition(bool prependSpace = false)
where = (prependSpace ? " " : string.Empty) + "WHERE " + filter;
}

return where;
return where.Trim();
}

protected virtual string PrepareInsertIntoTableVariableForUpdateUserChange(TableColumnInfo[] userInterestedColumns, string columnsForUpdateOf)
protected virtual string PrepareInsertIntoTableVariableForUpdateChange(TableColumnInfo[] userInterestedColumns, string columnsForUpdateOf)
{
var exceptStatement = this.PrepareExceptStatement(userInterestedColumns);

var columnsForSelectFromTableVariable = userInterestedColumns.Select(c =>
{
var column = $"[{c.Name}]";
if (this.IncludeOldValues) column += $", (SELECT [{c.Name}] FROM DELETED)";
return column;
});

var columnsList = string.Join(", ", columnsForSelectFromTableVariable.ToList());
var insertIntoExceptTableStatement = this.PrepareInsertIntoModifiedRecordsTableStatement(userInterestedColumns);

var scriptForInsertInTableVariable = !string.IsNullOrEmpty(columnsForUpdateOf)
? string.Format(SqlScripts.InsertInTableVariableConsideringUpdateOf, columnsForUpdateOf, columnsList, ChangeType.Update, exceptStatement)
: string.Format(SqlScripts.InsertInTableVariable, columnsList, ChangeType.Update, exceptStatement);
? string.Format(SqlScripts.InsertInTableVariableConsideringUpdateOf, columnsForUpdateOf, ChangeType.Update, insertIntoExceptTableStatement)
: string.Format(SqlScripts.InsertInTableVariable, ChangeType.Update, insertIntoExceptTableStatement);

return scriptForInsertInTableVariable;
}

protected virtual IList<string> CreateSqlServerDatabaseObjects(
IEnumerable<TableColumnInfo> userInterestedColumns,
string columnsForUpdateOf,
int watchDogTimeOut)
protected virtual IList<string> CreateSqlServerDatabaseObjects(IEnumerable<TableColumnInfo> userInterestedColumns, string columnsForUpdateOf, int watchDogTimeOut)
{
var processableMessages = new List<string>();
var tableColumns = userInterestedColumns as IList<TableColumnInfo> ?? userInterestedColumns.ToList();

var columnsForTableVariable = this.PrepareColumnListForTableVariable(tableColumns);
var columnsForModifiedRecordsTable = this.PrepareColumnListForTableVariable(tableColumns, this.IncludeOldValues);
var columnsForExceptTable = this.PrepareColumnListForTableVariable(tableColumns, false);
var columnsForDeletedTable = this.PrepareColumnListForTableVariable(tableColumns, false);

using (var sqlConnection = new SqlConnection(_connectionString))
{
Expand Down Expand Up @@ -536,9 +526,9 @@ protected virtual IList<string> CreateSqlServerDatabaseObjects(
SqlScripts.CreateTrigger,
_dataBaseObjectsNamingConvention,
$"[{_schemaName}].[{_tableName}]",
columnsForTableVariable,
columnsForModifiedRecordsTable,
this.PrepareColumnListForSelectFromTableVariable(tableColumns),
this.PrepareInsertIntoTableVariableForUpdateUserChange(interestedColumns, columnsForUpdateOf),
this.PrepareInsertIntoTableVariableForUpdateChange(interestedColumns, columnsForUpdateOf),
declareVariableStatement,
selectForSetVariablesStatement,
sendInsertConversationStatements,
Expand All @@ -550,7 +540,9 @@ protected virtual IList<string> CreateSqlServerDatabaseObjects(
string.Join(", ", this.GetDmlTriggerType(_dmlTriggerType)),
this.CreateWhereCondifition(),
this.PrepareTriggerLogScript(),
this.ActivateDatabaseLoging ? " WITH LOG" : string.Empty);
this.ActivateDatabaseLoging ? " WITH LOG" : string.Empty,
columnsForExceptTable,
columnsForDeletedTable);

sqlCommand.ExecuteNonQuery();
this.WriteTraceMessage(TraceLevel.Verbose, $"Trigger {_dataBaseObjectsNamingConvention} created.");
Expand Down Expand Up @@ -616,26 +608,50 @@ protected virtual string RemoveLogOperations(string source)
return source;
}

protected virtual string PrepareExceptStatement(IReadOnlyCollection<TableColumnInfo> interestedColumns)
protected virtual string PrepareInsertIntoModifiedRecordsTableStatement(IReadOnlyCollection<TableColumnInfo> interestedColumns)
{
if (interestedColumns.Any(tableColumn =>
string.Equals(tableColumn.Type.ToLowerInvariant(), "timestamp", StringComparison.OrdinalIgnoreCase) ||
string.Equals(tableColumn.Type.ToLowerInvariant(), "rowversion", StringComparison.OrdinalIgnoreCase))) return "INSERTED";
string insertIntoExceptStatement;

var whereCondifition = this.CreateWhereCondifition();

var separatorNewColumns = new Separator(2, ",");
var sBuilderNewColumns = new StringBuilder();
var separatorOldColumns = new Separator(2, ",");
var sBuilderOldColumns = new StringBuilder();
var comma = new Separator(2, ",");
var sBuilderColumns = new StringBuilder();
foreach (var column in interestedColumns) sBuilderColumns.Append($"{comma.GetSeparator()}[{column.Name}]");

foreach (var column in interestedColumns)
var insertedAndDeletedTableVariable =
$"INSERT INTO @deletedTable SELECT {sBuilderColumns} FROM DELETED" + Environment.NewLine +
$"INSERT INTO @insertedTable SELECT {sBuilderColumns} FROM INSERTED" + Environment.NewLine + Environment.NewLine;

if (interestedColumns.Any(tableColumn => string.Equals(tableColumn.Type.ToLowerInvariant(), "timestamp", StringComparison.OrdinalIgnoreCase) || string.Equals(tableColumn.Type.ToLowerInvariant(), "rowversion", StringComparison.OrdinalIgnoreCase)))
{
sBuilderNewColumns.Append($"{separatorNewColumns.GetSeparator()}[m_New].[{column.Name}]");
sBuilderOldColumns.Append($"{separatorOldColumns.GetSeparator()}[m_Old].[{column.Name}]");
insertIntoExceptStatement =
insertedAndDeletedTableVariable +
$"INSERT INTO @exceptTable SELECT [RowNumber],{sBuilderColumns} FROM @insertedTable";
}
else
{
insertIntoExceptStatement =
insertedAndDeletedTableVariable +
$"INSERT INTO @exceptTable SELECT [RowNumber],{sBuilderColumns} FROM @insertedTable EXCEPT SELECT [RowNumber],{sBuilderColumns} FROM @deletedTable";
}

if (this.IncludeOldValues)
{
comma = new Separator(2, ",");
sBuilderColumns = new StringBuilder();
foreach (var column in interestedColumns)
{
sBuilderColumns.Append($"{comma.GetSeparator()}[{column.Name}]");
sBuilderColumns.Append($"{comma.GetSeparator()}(SELECT d.[{column.Name}] FROM @deletedTable d WHERE d.[RowNumber] = e.[RowNumber])");
}
}

var exceptStatement = $"(SELECT {sBuilderNewColumns} FROM INSERTED AS [m_New] {this.CreateWhereCondifition()} EXCEPT SELECT {sBuilderOldColumns} FROM DELETED AS [m_Old]) a";
var insertIntoModifiedRecordsTable =
insertIntoExceptStatement + Environment.NewLine + Environment.NewLine +
$"INSERT INTO @modifiedRecordsTable {Environment.NewLine}" +
$"SELECT {sBuilderColumns} FROM @exceptTable e {whereCondifition}";

return exceptStatement;
return insertIntoModifiedRecordsTable;
}

protected virtual IEnumerable<string> GetDmlTriggerType(DmlTriggerType dmlTriggerType)
Expand Down Expand Up @@ -683,33 +699,33 @@ protected virtual string PrepareColumnListForSelectFromTableVariable(IEnumerable
return string.Join(", ", columns.ToList());
}

protected virtual string PrepareColumnListForTableVariable(IEnumerable<TableColumnInfo> tableColumns)
protected virtual string PrepareColumnListForTableVariable(IEnumerable<TableColumnInfo> tableColumns, bool includeOldValues)
{
var columns = tableColumns.Select(tableColumn =>
{
if (string.Equals(tableColumn.Type.ToLowerInvariant(), "timestamp", StringComparison.OrdinalIgnoreCase))
{
var columnBinary = $"[{tableColumn.Name}] binary(8)";
if (this.IncludeOldValues) columnBinary += $", [{tableColumn.Name}_old] binary(8)";
if (includeOldValues) columnBinary += $", [{tableColumn.Name}_old] binary(8)";
return columnBinary;
}

if (string.Equals(tableColumn.Type.ToLowerInvariant(), "rowversion", StringComparison.OrdinalIgnoreCase))
{
var columnVarbinary = $"[{tableColumn.Name}] varbinary(8)";
if (this.IncludeOldValues) columnVarbinary += $", [{ tableColumn.Name}_old] varbinary(8)";
if (includeOldValues) columnVarbinary += $", [{ tableColumn.Name}_old] varbinary(8)";
return columnVarbinary;
}

if (!string.IsNullOrWhiteSpace(tableColumn.Size))
{
var columnWithSize = $"[{tableColumn.Name}] {tableColumn.Type}({tableColumn.Size})";
if (this.IncludeOldValues) columnWithSize += $", [{tableColumn.Name}_old] {tableColumn.Type}({tableColumn.Size})";
if (includeOldValues) columnWithSize += $", [{tableColumn.Name}_old] {tableColumn.Type}({tableColumn.Size})";
return columnWithSize;
}

var column = $"[{tableColumn.Name}] {tableColumn.Type}";
if (this.IncludeOldValues) column += $", [{tableColumn.Name}_old] {tableColumn.Type}";
if (includeOldValues) column += $", [{tableColumn.Name}_old] {tableColumn.Type}";
return column;
});

Expand Down Expand Up @@ -776,7 +792,7 @@ protected virtual string ConvertValueByType(IReadOnlyCollection<TableColumnInfo>
{
return this.SanitizeVariableName(userInterestedColumns, userInterestedColumn.Name) + oldNameExtension;
}

return $"CONVERT(NVARCHAR(MAX), {this.SanitizeVariableName(userInterestedColumns, userInterestedColumn.Name)}{oldNameExtension}{this.ConvertFormat(userInterestedColumn)})";
}

Expand Down
5 changes: 4 additions & 1 deletion TableDependency/TableDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,10 @@ protected virtual IList<string> GetUpdateOfColumnNameList(IUpdateOfModel<T> upda

dbColumnName = GetColumnNameFromModelProperty(tableColumns, propertyInfo.Name);
updateOfList.Add(dbColumnName);
}
continue;
}

updateOfList.Add(propertyInfo.Name);
}

return updateOfList;
Expand Down
4 changes: 2 additions & 2 deletions TableDependency/Utilities/ModelToTableMapperHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static IModelToTableMapper<T> GetModelMapperFromColumnDataAnnotation(IEnu
{
var modelPropertyInfosWithColumnAttribute = typeof(T)
.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public)
.Where(x => CustomAttributeExtensions.IsDefined(x, typeof(ColumnAttribute), false))
.Where(x => CustomAttributeExtensions.IsDefined((MemberInfo)x, typeof(ColumnAttribute), false))
.ToArray();

if (!modelPropertyInfosWithColumnAttribute.Any()) return null;
Expand All @@ -62,7 +62,7 @@ public static IModelToTableMapper<T> GetModelMapperFromColumnDataAnnotation(IEnu
}
}

return mapper.Count() > 0 ? mapper : null;
return mapper;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class DatabaseObjectCleanUpTestSqlServerModel
public class DatabaseObjectAutoCleanUpTest : SqlTableDependencyBaseTest
{
private static string _dbObjectsNaming;
private const string TableName = "SpiderManTable";
private const string TableName = "DatabaseObjectAutoCleanUpTestTable";

[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
Expand Down
Loading

0 comments on commit fb23925

Please sign in to comment.