/// <summary> /// Enables change history logging feature on the repository. /// </summary> /// <param name="changeHistoryFilePath">File path to store change history data. Multiple /// invocations of this method would add new files to the change history filegroup.</param> /// <example>This example shows how to enable and disable change history feature. On Vista or /// Windows Server 2008, you may have to run this sample as an administrator. /// <code> ///using System; ///using System.Linq; ///using Zentity.Administration; ///using Zentity.Core; ///using System.Threading; ///using System.ServiceProcess; /// ///namespace ZentitySamples ///{ /// public class Program /// { /// public static void Main(string[] args) /// { /// ZentityContext regularContext = new ZentityContext(); /// AdministrationContext adminContext = new AdministrationContext(); /// /// // Enabling and disabling change history is lengthy operation. Set a sufficiently /// // large timeout for the context commands. /// adminContext.CommandTimeout = 300; /// /// // Disable SQL Server Agent process before enabling or disabling the change history. /// ServiceController[] scServices; /// scServices = ServiceController.GetServices(); /// var v = scServices.Where /// (tuple => tuple.DisplayName == "SQL Server Agent (MSSQLSERVER)").FirstOrDefault(); /// if (v != null && v.Status == ServiceControllerStatus.Running) /// v.Stop(); /// Thread.Sleep(5000); /// /// bool isChangeHistoryEnabled = /// Convert.ToBoolean(regularContext.GetConfiguration("IsChangeHistoryEnabled")); /// /// Console.WriteLine("IsChangeHistoryEnabled: [{0}]", isChangeHistoryEnabled); /// /// if (isChangeHistoryEnabled) /// { /// Console.WriteLine("Disabling change history..."); /// adminContext.DisableChangeHistory(); /// } /// /// Console.WriteLine("Enabling change history..."); /// adminContext.EnableChangeHistory(@"C:\ChangeHistory.ndf"); /// /// // Restart the SQL Agent. /// if (v != null) /// v.Start(); /// } /// } ///} /// </code> /// </example> /// <remarks> /// Zentity change history logging relies on the 'Change Data Capture' feature of SQL /// Server 2008 and is thus available only on Developer, Enterprise and Enterprise /// Evaluation editions. /// <para> /// This method alters the backend database to create new filegroups and files if they /// are not already created. Since ALTER DATABASE statement is not allowed within /// multi-statement transaction, including this method in a TransactionScope, for example, /// may raise Exceptions. /// </para> /// <para> /// Also, if you are seeing any Transaction deadlock errors, turn off SQL Server Agent /// service, invoke this method and then turn on the agent again. /// </para> /// While enabling change history logging, each major table in Zentity database is enabled /// for change data capture. SQL Server automatically creates two jobs during this process, /// 1. to populate capture instances and 2. to periodically clean up the capture instances. /// Zentity derives its change history data from these capture instances. A background job, /// ProcessNextLSN, pulls data from the capture instances and populates a separate set of /// ‘Coupling’ tables. These coupling tables allow us to retain the historical data even /// after the capture instances are cleaned up. 'Coupling' tables are then mapped to the /// conceptual model of Zentity Change History Logging. The public API is generated by /// Entity Framework from the conceptual model. Figure below presents an overall picture. /// <br/> /// <img src="ChangeHistory.bmp"/> /// <para> /// <b>Data Loss Scenarios</b> /// <br/> /// Since Zentity processes CDC capture instances to retrieve the change history /// information, there is a possibility of data loss if the source capture instance is /// deleted before it is completely processed. This might happen if Change Data Capture is /// disabled on the database before all the changesets are processed. To print a list of /// changesets yet to be processed, execute a query similar to the following. /// <code language="SQL"> ///SELECT [start_lsn], [tran_end_time] ///FROM [cdc].lsn_time_mapping ///WHERE tran_id <> 0x00 ///EXCEPT ///SELECT Administration.fn_hexstrtovarbin([Id]) [start_lsn], [DateCreated] [tran_end_time] ///FROM [Administration].ChangeSet /// </code> /// Another scenario where there is a possibility of data loss is while altering the schema /// of Core.Resource table during Zentity Data Model updates. A new capture instance is /// created for Core.Resource table in response to the schema change. Since, SQL Server /// allows a maximum of two capture instances per table, an earlier capture instance is /// dropped if the capture instance count increases two. All data present in the dropped /// capture instance is thus lost. /// </para> /// <para> /// <b>Backup and Restore Scenario</b> /// <br/> /// During backup and restore of Zentity database, all configuration values and capture /// instances are restored. However, the jobs to populate and cleanup capture instances and /// 'ProcessNextLSN' are not restored on the target server. So, even though sys.databases /// and sys.tables show that the database and table are configured for change data capture, /// no entries are created in the capture instances and hence the Administration tables. /// To fix this, disable change history logging and then re-enable it again using either /// the stored procedures Administration.DisableChangeHistory and /// Administration.EnableChangeHistory or using the methods /// <see cref="Zentity.Administration.AdministrationContext.DisableChangeHistory"/> and /// <see cref="Zentity.Administration.AdministrationContext.EnableChangeHistory(string)"/>. /// </para> /// </remarks> public void EnableChangeHistory(string changeHistoryFilePath) { if (string.IsNullOrEmpty(changeHistoryFilePath)) { throw new ArgumentException( AdministrationResources.InvalidFilePath, "changeHistoryFilePath"); } using (EntityCommand cmd = (EntityCommand)this.Connection.CreateCommand()) { bool isConnectionOpenedHere = false; if (this.Connection.State == ConnectionState.Closed) { this.Connection.Open(); isConnectionOpenedHere = true; } cmd.CommandText = "AdministrationContext.EnableChangeHistory"; cmd.CommandType = CommandType.StoredProcedure; cmd.CommandTimeout = this.OperationTimeout; EntityParameter param = cmd.CreateParameter(); param.DbType = DbType.String; param.ParameterName = "ChangeHistoryFilePath"; param.Size = 512; param.Value = changeHistoryFilePath; cmd.Parameters.Add(param); cmd.ExecuteNonQuery(); if (isConnectionOpenedHere) { this.Connection.Close(); } } }