<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>hughpyle : Web Services Tips</title><link>http://blogs.msdn.com/hughpyle/archive/tags/Web+Services+Tips/default.aspx</link><description>Tags: Web Services Tips</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Timestamps</title><link>http://blogs.msdn.com/hughpyle/archive/2006/05/23/604795.aspx</link><pubDate>Tue, 23 May 2006 16:52:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:604795</guid><dc:creator>hpyle</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/hughpyle/comments/604795.aspx</comments><wfw:commentRss>http://blogs.msdn.com/hughpyle/commentrss.aspx?PostID=604795</wfw:commentRss><description>&lt;P&gt;&lt;FONT face=Arial size=2&gt;In one of the main scenarios for data integration with Groove:&amp;nbsp; you have a Groove Forms tool with a set of records, and a SQLServer table or a SharePoint list with a set of records, and you either want to synchronize from Groove to the other system, or from the other system into Groove, or bidirectionally.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;There are a couple ways to do this.&amp;nbsp; One is to listen for events from the source (or both systems, for bidirectional synch) and push the records which caused those events into the other system as soon as possible.&amp;nbsp; Another is to occasionally query each table, identify which records have changed, then push a batch of changes from one side to the other.&amp;nbsp; For a variety of reasons, this second strategy seems to be the approach most people take.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;With this query-based&amp;nbsp;approach, you need to be quite careful about timestamps.&amp;nbsp; So here's the gory detail.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial&gt;&lt;STRONG&gt;Groove Forms System Fields&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;Every record in a Groove Forms tool has some "system fields" which are always present:&lt;/FONT&gt;&lt;/P&gt;
&lt;div class=code&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_Created&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_CreatedBy&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_CreatedByURL&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_Modified&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_ModifiedBy&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_ModifiedByURL&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_RecordID&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_ParentID&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_Editors&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_Readers&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;_IgnoreUnread&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;Forms_Tool_grooveFormID&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;Forms_Tool_IPContents&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;/div&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;For now, let's just look at _Created and _Modified.&amp;nbsp; (The others are all interesting, but that'll wait for another time).&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;The _Created field is a date/time value (internally, it's stored as milliseconds since Unix epoch, like the JavaScript datevalue) which is the time at which this record was created.&amp;nbsp; The _Modified field stores the time when the record was last modified.&amp;nbsp; These values are set by the creator, or editor, of the record.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;Here's the problem: they're local time.&amp;nbsp; If another member of the workspace created or modified the record, their timestamps might be in the future, or in the past, compared to your local clock.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial&gt;&lt;STRONG&gt;Timestamp Query&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;Let's assume you're writing an integration application to be deployed on a Groove Data Bridge server, which is a member of the workspace(s) which need to be synchronized with (say) SQLServer.&amp;nbsp; Your application has a timer, and every few minutes, asks Groove for a list of the records which changed since you last synchronized.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Arial size=2&gt;This query would seem straightforward enough.&amp;nbsp; Find the Forms tool, then call QueryRecords:&lt;/FONT&gt;&lt;/P&gt;
&lt;div class=code&gt;&lt;FONT face=Arial size=2&gt;&lt;FONT size=1&gt;&lt;FONT color=#008080 size=1&gt;
&lt;P&gt;DateTime&lt;/FONT&gt;&lt;FONT size=1&gt; lastQueried; // from the last time we queried for data&lt;/P&gt;
&lt;P&gt;&lt;/FONT&gt;GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;RecordQuery&lt;/FONT&gt;&lt;FONT size=1&gt; recordQuery = &lt;/FONT&gt;&lt;FONT color=#0000ff size=1&gt;new&lt;/FONT&gt;&lt;FONT size=1&gt; GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;RecordQuery&lt;/FONT&gt;&lt;FONT size=1&gt;();&lt;/P&gt;
&lt;P&gt;// set query properties, including this:&lt;/FONT&gt;&lt;FONT size=1&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;&lt;FONT size=1&gt;
&lt;P&gt;recordQuery.WhereClause = &lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;String&lt;/FONT&gt;&lt;FONT size=1&gt;.Format(&lt;/FONT&gt;&lt;FONT color=#800000 size=1&gt;"_Modified &amp;gt; #{0:yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'}#"&lt;/FONT&gt;&lt;FONT size=1&gt;, lastQueried.ToUniversalTime());&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;
&lt;P&gt;&lt;/FONT&gt;&lt;FONT color=#008000 size=1&gt;// Ask service to query records&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#008000 size=1&gt;&lt;FONT color=#000000&gt;lastQueried = DateTime.Now;&lt;/FONT&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;
&lt;P&gt;GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;FormsRecordDataSet&lt;/FONT&gt;&lt;FONT size=1&gt; fds = formsService.QueryRecords(recordQuery);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;/div&gt;
&lt;P&gt;&lt;FONT size=1&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;Unfortunately this won't work.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;First issue: "lastQueried" is local to your application, which might be running on a different machine from Groove (which is quite a common scenario with EDB server-based integration);&amp;nbsp; instead, you might want to scan your known records instead to find the high-water-mark of _Modified dates which you know about.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Second issue: the query says &amp;gt; where it should say &amp;gt;=.&amp;nbsp; The internal timestamp is milliseconds, but this query only asks for seconds-based matching, so it might miss values.&amp;nbsp; Or you might query against the milliseconds value directly.&lt;/P&gt;
&lt;P&gt;Third issue, the kicker: that _Modified date corresponds to the client's clock where the modification originated.&amp;nbsp; Even if their machine clock was synchronized with yours, it's entirely possible that the user was offline when they made this change, then connected to the network a few days later, and you're only now seeing their changed record; and this simplistic query will never see it.&lt;/P&gt;
&lt;P&gt;&lt;FONT size=3&gt;&lt;STRONG&gt;Forms_Tool_EDBSeqTime&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;What you need is a monotonic timestamp.&amp;nbsp; We implemented this on the Data Bridge server only -- not in the client software,&amp;nbsp;because there's a small performance&amp;nbsp;overhead&amp;nbsp;-- using a special field called "Forms_Tool_EDBSeqTime".&lt;/P&gt;
&lt;P&gt;On the Data Bridge, make sure you have installed the "Forms Tool Sequence Time Extension" (documentation in the data bridge administrator's guide).&amp;nbsp; This adds a local-only field to every Forms record, and updates its value every time an update is &lt;U&gt;received&lt;/U&gt;.&amp;nbsp; This means that the Forms_Tool_EDBSeqTime value is a timestamp local to the server, not the originator of a change.&lt;/P&gt;
&lt;P&gt;So, to implement our query properly:&lt;/P&gt;
&lt;div class=code&gt;&lt;FONT face=Arial size=2&gt;&lt;FONT size=1&gt;&lt;FONT color=#008080 size=1&gt;
&lt;P&gt;DateTime&lt;/FONT&gt;&lt;FONT size=1&gt; lastHighWaterMark = (...last known value...); // we usually want to persist this somewhere...&lt;/P&gt;
&lt;P&gt;&lt;/FONT&gt;GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;RecordQuery&lt;/FONT&gt;&lt;FONT size=1&gt; recordQuery = &lt;/FONT&gt;&lt;FONT color=#0000ff size=1&gt;new&lt;/FONT&gt;&lt;FONT size=1&gt; GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;RecordQuery&lt;/FONT&gt;&lt;FONT size=1&gt;();&lt;/P&gt;
&lt;P&gt;// set query properties, including this:&lt;/FONT&gt;&lt;FONT size=1&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;&lt;FONT size=1&gt;
&lt;P&gt;recordQuery.WhereClause = &lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;String&lt;/FONT&gt;&lt;FONT size=1&gt;.Format(&lt;/FONT&gt;&lt;FONT color=#800000 size=1&gt;"Forms_Tool_EDBSeqTime &amp;gt;= #{0:yyyy'-'MM'-'dd'T'HH':'mm':'ss.ffff'Z'}#"&lt;/FONT&gt;&lt;FONT size=1&gt;, lastHighWaterMark.ToUniversalTime());&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;
&lt;P&gt;&lt;/FONT&gt;&lt;FONT color=#008000 size=1&gt;// Ask service to query records&lt;/FONT&gt;&lt;FONT color=#008000 size=1&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;FONT size=1&gt;
&lt;P&gt;GrooveForms2.&lt;/FONT&gt;&lt;FONT color=#008080 size=1&gt;FormsRecordDataSet&lt;/FONT&gt;&lt;FONT size=1&gt; fds = formsService.QueryRecords(recordQuery);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=1&gt;lastHighWaterMark = /* highest Forms_Tool_EDBSeqTime from known data */;&lt;/FONT&gt;&lt;/div&gt;&lt;/FONT&gt;
&lt;P&gt;&lt;FONT size=3&gt;&lt;STRONG&gt;Alternatives&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Event-based, rather than query-based,&amp;nbsp;synchronization doesn't requre the Forms_Tool_EDBSeqTime monotonic timestamp.&amp;nbsp; But implementation (as always) is another subject, for another day.&lt;/P&gt;
&lt;P&gt;&lt;FONT size=1&gt;&amp;nbsp;&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=604795" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/hughpyle/archive/tags/Web+Services+Tips/default.aspx">Web Services Tips</category></item></channel></rss>