Answered by:
Issue SyncFramework 2.1 - Auto Increment (Identity) Columns that are outside the Primary Key

Question
-
If you use an Identity Column that is outside the primary key. The Sync Framework is creating an update stored procedure that is attempting to update the identity column. While for inserts TSQL can leverage: SET IDENTITY_INSERT [ColumnName] ON, there is no such animal for updates.
If the column was part of the PrimaryKey, it would not be part of the Update Stored Procedure.
Here is my Lead Table, notice LeadKey is an IDENTITY column, but it is outside the PrimaryKey, which is LeadID a GUID.
CREATE TABLE [dbo].[Leads]( [LeadID] [uniqueidentifier] NOT NULL, [LeadKey] [int] IDENTITY(0,1) NOT NULL, [Authenticated] [bit] NULL, [Name] [nvarchar](100) NULL, [Email] [nvarchar](50) NULL, [Phone] [nvarchar](30) NULL, [UserAgentID] [int] NULL, CONSTRAINT [PK_Leads] PRIMARY KEY CLUSTERED ( [LeadID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
Here is the Update Stored Proc created from my Lead Table
CREATE PROCEDURE [Leads_update] @P_1 UniqueIdentifier, @P_2 Int, @P_3 Bit, @P_4 NVarChar(100), @P_5 NVarChar(50), @P_6 NVarChar(30), @P_7 Int, @sync_force_write Int, @sync_min_timestamp BigInt, @sync_row_count Int OUTPUT AS BEGIN SET @sync_row_count = 0; UPDATE [Leads] SET [LeadKey] = @P_2, [Authenticated] = @P_3, [Name] = @P_4, [Email] = @P_5, [Phone] = @P_6, [UserAgentID] = @P_7 FROM [Leads] [base] JOIN [Leads_tracking] [side] ON [base].[LeadID] = [side].[LeadID] WHERE ([side].[local_update_peer_timestamp] <= @sync_min_timestamp OR @sync_force_write = 1) AND ([base].[LeadID] = @P_1); SET @sync_row_count = @@ROWCOUNT; END
In order to view this yourself:
- Open SQL Profiler
- Start a New Trace
- Select the Use Template TSQL
- Execute your action...
You'll find it dies on the creation of the update Stored Proc. I'll send an 'Update' ;) after I figure out how to fix this. But if anyone on the Sync Team could offer suggestions that would be awesome!
Thanks:
Dylan
Dylan Phillips -- Update Getting closer to the root causeMicrosoft.Synchronization.Data.SqlServer.SqlSyncProcedureHelper.BuildUpdateCommand()
Notice we are using this._tableDesc.NonPkMutableColumns(). My AutoIncrement (immutable) is coming up in this enumeration.
private SqlCommand BuildUpdateCommand() { if (!this._tableDesc.HasNonPkMutableColumns) { return this.BuildInsertCommand(); } SqlCommand sqlCommand = new SqlCommand(); sqlCommand.CommandTimeout = this._commandTimeout; StringBuilder builder = new StringBuilder(); this.AddColumnParametersToCommand(sqlCommand); SqlParameter parameter = new SqlParameter("@sync_force_write", SqlDbType.Int); sqlCommand.Parameters.Add(parameter); SqlParameter parameter2 = new SqlParameter("@sync_min_timestamp", SqlDbType.BigInt); sqlCommand.Parameters.Add(parameter2); SqlParameter parameter3 = new SqlParameter("@sync_row_count", SqlDbType.Int); parameter3.Direction = ParameterDirection.Output; sqlCommand.Parameters.Add(parameter3); builder.Append(ClearSyncRowCountParameter(parameter3)).Append("; "); builder.Append("UPDATE " + this._tableDesc.LocalName + " SET " + QueryStringUtils.CommaSeparatedUpdateFromParameters(this._tableDesc.NonPkMutableColumns)); builder.Append(" FROM " + this._tableDesc.LocalName + " " + BaseTableAlias + " JOIN " + this._trackingTableName.QuotedString + " " + TrackingTableAlias + " ON "); builder.Append(QueryStringUtils.JoinTwoTablesOnClause(this._tableDesc.PkColumns, BaseTableAlias, TrackingTableAlias)); builder.Append(" WHERE (" + TrackingTableAlias + "." + this._trackingColNames.LocalUpdatePeerTimestamp + " <= @sync_min_timestamp OR @sync_force_write = 1) AND "); builder.Append("(" + QueryStringUtils.WhereColumnAndParameters(this._tableDesc.PkColumns, BaseTableAlias) + ");"); builder.Append(" SET " + parameter3.ParameterName + " = @@ROWCOUNT;"); sqlCommand.CommandText = builder.ToString(); return sqlCommand; }
So, I'm digging into DbSyncTableDescription, and now things get a bit uglier do to the impact of reflect, but I think I'm getting close.DbSyncTableDescription.NonPkMutableColumns returns and internal class <get_NonPkMutableColumns>internal virtual IEnumerable<DbSyncColumnDescription> NonPkMutableColumns { get { <get_NonPkMutableColumns>d__f _f = new <get_NonPkMutableColumns>d__f(-2); _f.<>4__this = this; return _f; } }
And get_NonPkMutableColumns.MoveNext() looks like where we are doing the checking for (Not PK) and (Not Mutable). Notice in Label_0044, we are only checking '.IsPrimaryKey', which works fine for the (NonPk) part of MutableColumns. But, it doesn't check for mutability...i.e. (.AutoIncrementStepSpecified).private bool MoveNext() { bool flag; try { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<>7__wrap11 = this.<>4__this.MutableColumns.GetEnumerator(); this.<>1__state = 1; goto Label_0080; case 2: this.<>1__state = 1; goto Label_0080; default: goto Label_00A7; } Label_0044: this.<colDesc>5__10 = this.<>7__wrap11.Current; if (!this.<colDesc>5__10.IsPrimaryKey) { this.<>2__current = this.<colDesc>5__10; this.<>1__state = 2; return true; } Label_0080: if (this.<>7__wrap11.MoveNext()) { goto Label_0044; } this.<>1__state = -1; if (this.<>7__wrap11 != null) { this.<>7__wrap11.Dispose(); } Label_00A7: flag = false; } fault { ((IDisposable) this).Dispose(); } return flag; }
- Edited by Dylan C Phillips Friday, January 28, 2011 2:58 PM Formatting Fixes
Friday, January 28, 2011 2:00 PM
Answers
-
Hi Dylan
The problem you are facing is due to a limitation in our current version of Microsoft Synchronization Framework 2.1, where the Database provider does not synchronize correctly identity columns that are primary keys. There is however a work around which is to add the identity column to a cluster index.
You would have to modify your table and add the LeadKey to the cluster index as follows:
CREATE TABLE [dbo].[Leads]( [LeadID] [uniqueidentifier] NOT NULL, [LeadKey] [int] IDENTITY(0,1) NOT NULL, [Authenticated] [bit] NULL, [Name] [nvarchar](100) NULL, [Email] [nvarchar](50) NULL, [Phone] [nvarchar](30) NULL, [UserAgentID] [int] NULL, CONSTRAINT [PK_Leads] PRIMARY KEY CLUSTERED ( [LeadID] ASC, [LeadKey] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
Maria del Mar Alvarez Rohena Microsoft Sync Framework- Edited by María del Mar Alvarez Rohena Wednesday, February 2, 2011 6:30 PM formatting
- Proposed as answer by María del Mar Alvarez Rohena Wednesday, February 2, 2011 6:30 PM
- Marked as answer by Mahesh DudgikarMicrosoft employee Thursday, February 17, 2011 9:06 PM
Wednesday, February 2, 2011 6:30 PM
All replies
-
Hi Dylan
One of the limitations of your DB Sync Provider is that it does not handle identity columns that are non-primary keys. There is a work around to this issue which is to add LeadKey to the your primary key clustered index as follows:
CREATE TABLE [dbo].[Leads]( [LeadID] [uniqueidentifier] NOT NULL, [LeadKey] [int] IDENTITY(0,1) NOT NULL, [Authenticated] [bit] NULL, [Name] [nvarchar](100) NULL, [Email] [nvarchar](50) NULL, [Phone] [nvarchar](30) NULL, [UserAgentID] [int] NULL, CONSTRAINT [PK_Leads] PRIMARY KEY CLUSTERED ( [LeadID] ASC,
[LeadKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
with this modification, things should work.
Maria del Mar Alvarez Rohena Microsoft Sync Framework- Edited by María del Mar Alvarez Rohena Tuesday, February 1, 2011 8:47 PM html
- Proposed as answer by María del Mar Alvarez Rohena Tuesday, February 1, 2011 8:48 PM
Tuesday, February 1, 2011 8:44 PM -
Hi Dylan
The problem you are facing is due to a limitation in our current version of Microsoft Synchronization Framework 2.1, where the Database provider does not synchronize correctly identity columns that are primary keys. There is however a work around which is to add the identity column to a cluster index.
You would have to modify your table and add the LeadKey to the cluster index as follows:
CREATE TABLE [dbo].[Leads]( [LeadID] [uniqueidentifier] NOT NULL, [LeadKey] [int] IDENTITY(0,1) NOT NULL, [Authenticated] [bit] NULL, [Name] [nvarchar](100) NULL, [Email] [nvarchar](50) NULL, [Phone] [nvarchar](30) NULL, [UserAgentID] [int] NULL, CONSTRAINT [PK_Leads] PRIMARY KEY CLUSTERED ( [LeadID] ASC, [LeadKey] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
Maria del Mar Alvarez Rohena Microsoft Sync Framework- Edited by María del Mar Alvarez Rohena Wednesday, February 2, 2011 6:30 PM formatting
- Proposed as answer by María del Mar Alvarez Rohena Wednesday, February 2, 2011 6:30 PM
- Marked as answer by Mahesh DudgikarMicrosoft employee Thursday, February 17, 2011 9:06 PM
Wednesday, February 2, 2011 6:30 PM -
This can also be done within the SyncFramework, it's just ugly. Any word on when the AutoNumber outside the primary key issue will be fixed?
DbSyncTableDescription leadsTable = SqlSyncDescriptionBuilder.GetDescriptionForTable("Leads", azureConnection); //Forcing the LeadKey into the Primary Key leadsTable.Columns["[LeadKey]"].IsPrimaryKey = true;
Dylan PhillipsFriday, February 11, 2011 12:54 PM -
While there is a workaround like you noticed, I would like to ask why there is a need for this column and if so, why should it be an identity column in your schema?
Currently there is no plan of supporting auto-identity range management in the SyncFx for the near future.
This posting is provided AS IS with no warranties, and confers no rightsWednesday, February 16, 2011 8:44 AM