<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>Database Considerations</title>
        <link>http://geekswithblogs.net/chrisfalter/category/4242.aspx</link>
        <description>Occasionally I get an inspiration on how to solve a problem with interesting SQL syntax or sound schema design.</description>
        <language>en-US</language>
        <copyright>Chris Falter</copyright>
        <managingEditor>chrisfalter@yahoo.com</managingEditor>
        <generator>Subtext Version 0.0.0.0</generator>
        <item>
            <title>How To Search Encrypted Text in SQL Server 2005/2008</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx</link>
            <description>&lt;br /&gt;
&lt;p&gt;Recently I was discussing SQL Server encryption with some friends who have been using it to encrypt short strings such as Social Security numbers at their shop.  I commented, "Just try searching those Social Security Numbers," they shared my lamentation, and we moved on to other subjects.  Later that evening, though, I thought there must be a way to search those wretched encrypted blocks--&lt;em&gt;somehow&lt;/em&gt;--and worked out the solution you are about to read.  &lt;/p&gt;
&lt;h4&gt;The Problem&lt;/h4&gt;
&lt;p&gt;The difficulty lies in the fact that you cannot compare varbinaries in a predicate.  Combine this with the fact that encrypted text is stored as a varbinary, and the result is that you cannot search the varbinary encrypted text, even if you run the search term through the encryption algorithm before performing the comparison.  &lt;/p&gt;
&lt;p&gt;To illustrate, suppose you have a Person table that stores a name and a Social Security Number (SSN) encrypted with a certificate, defined like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[Person]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Id] [int] &lt;/font&gt;&lt;font color="#0000ff"&gt;IDENTITY&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;) NOT NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [SSN] [varbinary]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#808080"&gt; NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Name] [varchar]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt;&lt;font color="#000000"&gt; [PK_Person] &lt;/font&gt;&lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;[Id] &lt;/font&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;It would be nice if you could just encrypt the search text and build a predicate that compares the term and the encrypted column, like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;declare&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN &lt;/font&gt;&lt;font color="#0000ff"&gt;varbinary&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;set&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN &lt;/font&gt;&lt;font color="#808080"&gt;= &lt;/font&gt;&lt;font color="#ff00ff"&gt;ENCRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;select&lt;/font&gt;&lt;font color="#000000"&gt; name&lt;/font&gt;&lt;font color="#808080"&gt;, &lt;/font&gt;&lt;font color="#ff00ff"&gt;CONVERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#ff00ff"&gt;DECRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;font color="#808080"&gt;) &lt;/font&gt;&lt;font color="#0000ff"&gt;as&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;from&lt;/font&gt;&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;where&lt;/font&gt;&lt;font color="#000000"&gt; ssn &lt;/font&gt;&lt;font color="#808080"&gt;=&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned previously, though, this approach simply does not work.  &lt;/p&gt;
&lt;p&gt;Of course, you &lt;em&gt;could&lt;/em&gt; make the predicate succeed by setting the datatype of the encrypted text column to char(128), and then converting the encryption products (stored SSNs plus the search term) from varbinary to char(128) before using them.  However, indexing a column that large, and performing comparisons with it, would consume a lot of disk and processing resources.&lt;/p&gt;
&lt;h4&gt;The Solution&lt;/h4&gt;
&lt;p&gt;Instead of converting the large encrypted block into char(128) data, you can work with the hash of the SSN instead.  If you calculate the hash using the SHA1 algorithm, the hash will require only 20 bytes of storage, so the burden on disk and processing resources will drop significantly.  And since the SHA1 algorithm is considered to be very secure, you will not add any significant security risk to your solution.  From start to finish, here's how the approach will work:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Include a char(20) column in your table to hold the hash of the SSN.&lt;/strong&gt;  The new table definition will look like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[Person]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Id] [int] &lt;/font&gt;&lt;font color="#0000ff"&gt;IDENTITY&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;) NOT NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [SSN] [varbinary]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#808080"&gt; NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Name] [varchar]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Hash] [char]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt;&lt;font color="#000000"&gt; [PK_Person] &lt;/font&gt;&lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;[Id] &lt;/font&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;You should also index the Hash column to improve the performance of searches by SSN, of course. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Whenever you insert a record, store the hash of the SSN along with the encrypted bytes.&lt;/strong&gt;  Your insert stored procedure should use the built-in HASHBYTES function, like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE PROCEDURE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[usp_Insert_Person] &lt;br /&gt;
  @ssn &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  @name &lt;/font&gt;&lt;font color="#0000ff"&gt;varchar&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;AS&lt;br /&gt;
BEGIN&lt;/font&gt;&lt;br /&gt;
  insert into&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;SSN&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; Name&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;Hash&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  values&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;ENCRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @name&lt;/font&gt;&lt;font color="#808080"&gt;, &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;HASHBYTES&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'SHA1'&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;)))&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;END&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. When you search by SSN, compare the search term hash to the stored hash in your predicate&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE PROCEDURE&lt;/font&gt;&lt;font color="#000000"&gt; usp_Search_Person_BySsn &lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  @ssn &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;AS&lt;br /&gt;
BEGIN&lt;/font&gt;&lt;br /&gt;
&lt;font color="#008000"&gt;  --SET NOCOUNT ON added to prevent extra result sets from&lt;br /&gt;
  --interfering with SELECT statements.&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  SET NOCOUNT ON&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;font color="#0000ff"&gt;  declare&lt;/font&gt;&lt;font color="#000000"&gt; @hash &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  set&lt;/font&gt;&lt;font color="#000000"&gt; @hash &lt;/font&gt;&lt;font color="#808080"&gt;= &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;HASHBYTES&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'SHA1'&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;));&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  select &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;DECRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;cert_id&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certkey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; ssn&lt;/font&gt;&lt;font color="#808080"&gt;)) &lt;/font&gt;&lt;font color="#0000ff"&gt;as&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; name&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  from&lt;/font&gt;&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  where Hash&lt;/font&gt;&lt;font color="#808080"&gt;=&lt;/font&gt;&lt;font color="#000000"&gt; @hash&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;END&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This approach has worked well for me in my experimentation, and I recommend it to you if you need to search on an encrypted field.  You should note, however, that the approach does have a limitation: you cannot search on an arbitrary substring of the encrypted text.  In other words, a "begins with" or "contains" search is not possible.  As long as you can live with this limitation, you should be able to employ this approach for your own encrypted data.&lt;/p&gt;
&lt;p&gt;As always, I invite you, my reader, to leave a comment if you have found this useful or if you can think of an improvement.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=125658"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=125658" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/125658.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx</guid>
            <pubDate>Mon, 06 Oct 2008 05:14:56 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/125658.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx#feedback</comments>
            <slash:comments>5</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/125658.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/125658.aspx</trackback:ping>
        </item>
        <item>
            <title>.NET Entity Framework: Is It Ready for Web Prime-Time?</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx</link>
            <description>&lt;p&gt;Our shop has been looking for a way to simplify the interaction between our domain layer and data layer.  The data layer uses typed datasets for all its operations.  When it retrieves data, it populates a dataset; when it stores data, it checks the DataRowState of all rows in the dataset, and calls the appropriate stored procedure to insert, update, or delete the data.  The domain layer then uses dataset elements as the in-memory backing store for properties and collections.  A collection typically uses a DataTable, for example, and a collection member would use a DataRow in the DataTable.&lt;/p&gt;
&lt;p&gt;This arrangement has some advantages:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Between requests, domain objects can cache their state in session by caching a dataset. &lt;/li&gt;
    &lt;li&gt;DataRowState can inform the logic about which CRUD operation to perform on a DataRow.  Unmodified data can also be skipped over, yielding improved efficiency. &lt;/li&gt;
    &lt;li&gt;We have considerable freedom in designing domain classes.  We can make a property read-only, for example, if it corresponds to an immutable database field, such as an identity column. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This arrangement also has a big disadvantage, however.  We end up with lots of properties that look like this:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;public&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: blue"&gt;string&lt;/span&gt;&lt;span style="COLOR: black"&gt; Phone&lt;br /&gt;
{&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;get&lt;/span&gt;&lt;span style="COLOR: black"&gt; { &lt;/span&gt;&lt;span style="COLOR: blue"&gt;return&lt;/span&gt;&lt;span style="COLOR: black"&gt; dr.PHONE; }&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set&lt;/span&gt;&lt;span style="COLOR: black"&gt; { dr.PHONE = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;value&lt;/span&gt;&lt;span style="COLOR: black"&gt;; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;This property's one and only use is to map a domain attribute (an entity's phone number) to a particular field in a DataRow &lt;em&gt;(dr)&lt;/em&gt;.  Our domain layer has hundreds of these pass-through properties, which are a very verbose and not especially readable way of mapping between the domain and data layers.  So we've been looking for a better way to handle this mapping.&lt;/p&gt;
&lt;p&gt;Along comes the &lt;a href="http://msdn.microsoft.com/en-us/data/aa937723.aspx"&gt;.NET Entity Framework&lt;/a&gt; (EF), which provides a tool that creates a domain-to-data map in XML format.  Our first reaction was: this is just what the doctor ordered!  And supported by Microsoft, to boot.  &lt;/p&gt;
&lt;p&gt;On closer examination, though, the EF toolset has some growing up to do before we could consider using it in our web applications.  It can generate only public get/set accessors in the entity domain model (EDM) classes, for example.  You cannot make a property read-only, or internal/protected/private, or virtual.  You cannot map a non-generated property to a database field.  And programming in a straitjacket was not what we had in mind for a new approach.&lt;/p&gt;
&lt;p&gt;Second, EDM classes generated by EF do not have any supported way to cache their state.  A Microsoftie is working on &lt;a href="http://code.msdn.microsoft.com/entitybag"&gt;a tool ("Perseus")&lt;/a&gt; that can serialize the state of an entity using an EntityBag&amp;lt;T&amp;gt; instance, but it seems &lt;a href="http://blogs.msdn.com/dsimmons/archive/2008/01/28/entitybag-wrap-up-and-future-directions.aspx"&gt;somewhat incomplete&lt;/a&gt; (and definitely not supported) at the moment.  Since our web apps do cache domain entity state, adopting the EF right now would entail an important risk.&lt;/p&gt;
&lt;p&gt;What alternatives exist?  We can license &lt;a href="http://www.ideablade.com/DevForceEF_summary.html"&gt;DevForce EF&lt;/a&gt;, which is built on top of the EF and seems to offer pretty much everything that it lacks.  Or we can try &lt;a href="http://sourceforge.net/projects/nhibernate"&gt;NHibernate&lt;/a&gt;, an open-source .NET port of the Hibernate tool which is quite popular in the Java development world.&lt;/p&gt;
&lt;p&gt;Or maybe we can just wait for the EF to mature.  Microsoft has &lt;a href="http://blogs.msdn.com/dsimmons/archive/2008/05/17/why-use-the-entity-framework.aspx"&gt;big plans&lt;/a&gt; for the EF, and intends to make REST-oriented web services, Reporting Services, workflows, etc. EDM-aware.  So an EF investment today can yield important dividends in the future.&lt;/p&gt;
&lt;p&gt;Even though I cannot render a definitive answer, I hope that this discussion will help readers make an informed discussion about their &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt; choices in the .NET world.  What do you think?  Leave a comment with your insights or questions!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122423"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122423" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122423.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx</guid>
            <pubDate>Tue, 27 May 2008 14:50:29 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122423.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122423.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122423.aspx</trackback:ping>
        </item>
        <item>
            <title>How To: Encrypt and Manage Documents with SQL Server 2005</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx</link>
            <description>&lt;p&gt;When one of our departments decided to store sensitive reports, the architecture I first considered was storing them, unencrypted, in our imaging system.  The imaging system cannot manage encrypted documents, but its security capabilities allow it to to deny unauthorized users access to the reports.  This ability to restrict access seemed to meet our security requirements.  &lt;/p&gt;
&lt;p&gt;Then I started asking questions about data backups.  Offsite backups can be a major security vulnerability, as we have recently learned.  The names, addresses, social insurance numbers, and (in many cases) bank account details of almost half of England &lt;a href="http://www.cnn.com/2007/WORLD/europe/11/21/britain.personal/index.html"&gt;ended up who-knows-where last year because database backups disappeared in the postal system&lt;/a&gt;.  And our company, like any responsible organization, backs up our data and stores it offsite.  So leaving the sensitive data unencrypted was not acceptable.&lt;/p&gt;
&lt;p&gt;Fortunately, SQL Server 2005 comes with native encryption capabilities that are simple to use. In the absence of an affordable imaging system with built-in encryption capabilities, here's the approach I hammered out:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Create a table in a SQL Server 2005 database to store the encrypted documents. &lt;/li&gt;
    &lt;li&gt;Create a symmetric key in the same database.  To facilitate recovery from database failures, store the instructions for re-creating the key in a safe, remote location.  &lt;/li&gt;
    &lt;li&gt;Create stored procedures that use the key to encrypt/decrypt documents that are stored in the table. &lt;/li&gt;
    &lt;li&gt;Create a service account under which the service that stores the report will run. &lt;/li&gt;
    &lt;li&gt;Create a (presumably internal) website for displaying the reports.
    &lt;ul&gt;
        &lt;li&gt;Use the same service account for the site's app pool identity. &lt;/li&gt;
        &lt;li&gt;Restrict access to authorized groups via Windows authentication. &lt;/li&gt;
        &lt;li&gt;Require secure http so that a network trace cannot intercept decrypted documents on their way to the browser. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;On SQL Server, deny permissions for the stored procedures and table to anyone other than the service account.  The security context of the backup job must also be granted the db_backupoperator role on the database, of course. &lt;/li&gt;
    &lt;li&gt;Implement network encryption between SQL Server and any services that insert or select sensitive documents, using &lt;a href="http://support.microsoft.com/kb/816514"&gt;IPSec&lt;/a&gt; or &lt;a href="http://support.microsoft.com/kb/316898"&gt;SQL Server Network Encryption&lt;/a&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below is a diagram of the system that allows authorized users to view sensitive documents.&lt;/p&gt;
&lt;p&gt;&lt;img height="256" alt="Viewing Sensitive Documents" width="714" src="/images/geekswithblogs_net/chrisfalter/SensitiveDocuments/Doc Viewer Network Diagram(1).jpg" /&gt;&lt;/p&gt;
&lt;p&gt;There is scant MSDN documentation for accomplishing the first 3 steps, but the rest are well understood in the world of Microsoft development.  The remainder of this article will therefore focus on creating and managing the tables, keys, and stored procedures.&lt;/p&gt;
&lt;h4&gt;Step #1: Create a Table to Store the Encrypted Documents&lt;/h4&gt;
&lt;p&gt;Several considerations drive the design of the table:  &lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;The column that stores a block of encrypted data must have the datatype of varbinary(8000) because the result of an encryption operation is always a varbinary(8000).  &lt;/li&gt;
    &lt;li&gt;Unless you are dealing with very small documents, 8000 bytes will not hold an entire document.  Therefore a single document will require multiple records, which will be associated with one another by a common unique identifier.  A foreign key to the business entity associated with the document would be a good candidate.  In our system, the encrypted document is associated with an "Order," so the identifier is an OrderId. &lt;/li&gt;
    &lt;li&gt;In order to thwart brute force decryption attacks, you should "salt" the encryption with a unique integer for each report.  Unless your sensitive documents are very large, the foreign key (OrderId) will serve the purpose nicely. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the table definition that emerges from these considerations:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt; [dbo].[EncryptedReport](&lt;br /&gt;
   [OrderId] [int] &lt;font color="#808080"&gt;&lt;font color="#666699"&gt;NOT NULL&lt;/font&gt;,&lt;/font&gt;&lt;br /&gt;
   [BlockNum] [int] &lt;font color="#808080"&gt;&lt;font color="#666699"&gt;NOT NULL&lt;/font&gt;,&lt;/font&gt;&lt;br /&gt;
   [Block] [varbinary](8000) &lt;font color="#666699"&gt;NULL&lt;/font&gt;,&lt;br /&gt;
 &lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt; [PK_EncryptedReport] &lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
(&lt;br /&gt;
   [OrderId] &lt;font color="#000080"&gt;&lt;font color="#0000ff"&gt;ASC,&lt;/font&gt;&lt;br /&gt;
&lt;/font&gt;   [BlockNum] &lt;font color="#000080"&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;/font&gt;)&lt;br /&gt;
) &lt;font color="#0000ff"&gt;ON&lt;/font&gt; [PRIMARY]&lt;/font&gt;&lt;/p&gt;
&lt;h4&gt;Step #2: Create a Symmetric Key&lt;/h4&gt;
&lt;p&gt;To encrypt data in SQL Server 2005, you may use a certificate or a symmetric key.  A certificate provides somewhat stronger protection than a symmetric key, so it is the encryption method of choice for a small amount of data (such as a Social Security Number). However, its far greater demand for server resources makes it impractical for large amounts of text or other data.  Therefore, if you are encrypting a sensitive document, you should create a symmetric key and encrypt it with a certificate.  At run-time, SQL Server will use the certificate to decrypt the key, then use the key to encrypt or decrypt the document.  &lt;/p&gt;
&lt;p&gt;&lt;img height="324" alt="Key Generation" width="775" src="/images/geekswithblogs_net/chrisfalter/SensitiveDocuments/Key Generation Design.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Before SQL Server can encrypt or decrypt a sensitive document for you, it must traverse a chain of encrypted secrets:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;A certificate available only to the SQL Server process identity encrypts SQL Server's Service Master Key. &lt;/li&gt;
    &lt;li&gt;The Service Master Key encrypts the Database Master Key. &lt;/li&gt;
    &lt;li&gt;The Database Master key encrypts a certificate ("certDocEncryption" in this example). &lt;/li&gt;
    &lt;li&gt;The certificate encrypts a symmetric key ("keyReportEncryption" in this example). &lt;/li&gt;
    &lt;li&gt;(Finally!) the symmetric key encrypts your sensitive documents. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQL Server stores all of these secrets for you, except for the process identity's certificate (which is stored in the Windows certificate store).  Consequently, you do not need to worry about making a "secret" available to your run-time environment by, for example, storing it in a configuration file.  Database backups will not include the process identity's certificate (which the operating system maintains for you), so the only way the bad guy that steals your backup can decrypt your sensitive documents is by a brute force attack.  &lt;/p&gt;
&lt;p&gt;This design does place certain responsibilities on your IT organization:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;You must be very careful about using this design with a virtual server.&lt;/strong&gt;  While creating an image of an entire virtual server  (Windows OS, SQL Server, and data) facilitates quick disaster recovery, it can also facilitate the theft of sensitive documents, since the virtual server image will have everything (from Windows certificate down to the symmetric key) that a hacker needs to read the data. &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;To recover data in the event of a server failure, you must be able to re-create the symmetric key.&lt;/strong&gt;  While it is impossible to back up a symmetric key, you can always create an identical key on &lt;em&gt;any&lt;/em&gt; instance of SQL Server 2005 by issuing the "CREATE SYMMETRIC KEY" statement with the same algorithm, key source, and identity value.  Consequently, you must store the statement used to create your symmetric key in a secure location, such as a safe deposit box at a local bank.  And although this statement is quite obvious, I'll say it anyway: don't store the statement with your data backups! &lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Step #3: Create Stored Procedures&lt;/h4&gt;
&lt;p&gt;Encrypting a sensitive document is a four-step process:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Open the symmetric key. &lt;/li&gt;
    &lt;li&gt;Break the document into blocks that, when encrypted, will consume less than 8000 bytes. When I applied &lt;a href="http://blogs.msdn.com/yukondoit/archive/2005/11/24/496521.aspx"&gt;Microsoft's published formula&lt;/a&gt; to my encryption key process, I concluded that 7876 bytes was the maximum size of unencrypted data that I could safely fit into a varbinary(8000). &lt;/li&gt;
    &lt;li&gt;Encrypt each block. &lt;/li&gt;
    &lt;li&gt;Insert each encrypted block into the table. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the definition of the stored procedure that does the trick:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;CREATE PROCEDURE &lt;/span&gt;&lt;span style="COLOR: black"&gt;[dbo].[usp_StoreReportWithEncryption] &lt;br /&gt;
    @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;/span&gt;&lt;span style="COLOR: black"&gt;, &lt;br /&gt;
    @ReportText &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;) = ''&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@textLength &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockSize &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;(8000)&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@keyGuid &lt;/span&gt;&lt;span style="COLOR: blue"&gt;uniqueidentifier&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Open &lt;/span&gt;&lt;span style="COLOR: black"&gt;Symmetric &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Key &lt;/span&gt;&lt;span style="COLOR: black"&gt;keyReportEncryption&lt;br /&gt;
        decryption &lt;/span&gt;&lt;span style="COLOR: blue"&gt;by &lt;/span&gt;&lt;span style="COLOR: black"&gt;certificate certDocEncryption&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@keyGuid = Key_GUID('keyReportEncryption')&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockSize = 7876&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@textLength = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;datalength&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@ReportText)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;WHILE &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx &amp;lt; @textLength&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block = EncryptByKey(@keyGuid, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;SUBSTRING&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@ReportText, @idx, @blockSize), 1, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;, @OrderId))&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;insert into &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport(OrderId, BlockNum, Block)&lt;br /&gt;
                &lt;/span&gt;&lt;span style="COLOR: blue"&gt;values&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@OrderId, @blockNum, @block)&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx = @idx + @blockSize&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum = @blockNum + 1&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;br /&gt;
END &lt;/span&gt;&lt;/div&gt;
&lt;p&gt;To decrypt the document, you must:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Open the symmetric key, then &lt;/li&gt;
    &lt;li&gt;Reassemble the document by
    &lt;ul&gt;
        &lt;li&gt;Decrypting each block, and &lt;/li&gt;
        &lt;li&gt;Concatenating the results.  &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the definition of the stored procedure that will fetch the document associated with a particular OrderId:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;CREATE PROCEDURE &lt;/span&gt;&lt;span style="COLOR: black"&gt;[dbo].[usp_FetchEncryptedReport] &lt;br /&gt;
    @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int &lt;/span&gt;&lt;span style="COLOR: black"&gt;= 0&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: green"&gt;-- SET NOCOUNT ON added to prevent extra result sets from&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: green"&gt;-- interfering with SELECT statements.&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;SET NOCOUNT ON&lt;/span&gt;&lt;span style="COLOR: black"&gt;;&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@numBlocks &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;)&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;(8000)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Open &lt;/span&gt;&lt;span style="COLOR: black"&gt;Symmetric &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Key &lt;/span&gt;&lt;span style="COLOR: black"&gt;keyReportEncryption&lt;br /&gt;
        decryption &lt;/span&gt;&lt;span style="COLOR: blue"&gt;by &lt;/span&gt;&lt;span style="COLOR: black"&gt;certificate certDocEncryption&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text = ''&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@numBlocks = (&lt;/span&gt;&lt;span style="COLOR: blue"&gt;select IsNull&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;(blocknum), 0) &lt;/span&gt;&lt;span style="COLOR: blue"&gt;from &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport &lt;/span&gt;&lt;span style="COLOR: blue"&gt;where &lt;/span&gt;&lt;span style="COLOR: black"&gt;OrderId = @OrderId)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;     &lt;/span&gt;&lt;span style="COLOR: blue"&gt;WHILE &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx &amp;lt;= @numBlocks&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block = (&lt;/span&gt;&lt;span style="COLOR: blue"&gt;Select &lt;/span&gt;&lt;span style="COLOR: black"&gt;Block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;from &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport &lt;/span&gt;&lt;span style="COLOR: blue"&gt;where &lt;/span&gt;&lt;span style="COLOR: black"&gt;OrderId = @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;and &lt;/span&gt;&lt;span style="COLOR: black"&gt;BlockNum = @rowIdx)&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text = @text + &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;), DecryptByKey(@block, 1, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;,@OrderId)))&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx = @rowIdx + 1&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Select &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;/span&gt;&lt;/div&gt;
&lt;h4&gt;Finishing Your Security Infrastructure&lt;/h4&gt;
&lt;p&gt;There is no such thing as a perfectly secure architecture that provides a 100% guarantee against the theft or compromise of your sensitive documents.  An authorized user can give up his network credentials to a social engineering attack; an unpatched or badly configured database server might allow a hacker to gain administrator access to it; a trusted network administrator can steal data outright.   What SQL Server 2005 does very well is protecting data "at rest"--i.e., sensitive documents stored in your database.  By following the first 3 steps in this article, you should be able to get a solution up and running quickly.  Just remember: make sure you implement other forms of network security (including the last 4 steps mentioned in the introduction) to make your entire infrastructure for handling sensitive documents resilient against theft and hacking.  &lt;/p&gt;
&lt;p&gt;If you have suggestions or questions, please leave a comment!  Thanks for reading; I trust you have learned something useful from this article.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122004"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122004" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122004.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx</guid>
            <pubDate>Thu, 08 May 2008 21:13:56 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122004.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122004.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122004.aspx</trackback:ping>
        </item>
        <item>
            <title>A T-SQL Stumper</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2007/12/15/117756.aspx</link>
            <description>&lt;p&gt;We have a web service that serves as a gateway to a remote service provider.  Part of the design is a log of all interactions between our service and the remote service.  We keep the log in a SQL Server table with this definition:&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE [dbo].[Request](&lt;br /&gt;
    [RequestId] [int] IDENTITY(1,1) NOT NULL,&lt;br /&gt;
    [TxCode] [char](2) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,&lt;br /&gt;
    [ResponseText] [varchar](max) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,&lt;br /&gt;
    [ResponseTime] [datetime] NULL,&lt;br /&gt;
    [CompanyId] [varchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,&lt;br /&gt;
 CONSTRAINT [PK_Request] PRIMARY KEY CLUSTERED &lt;br /&gt;
(&lt;br /&gt;
    [RequestId] ASC&lt;br /&gt;
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]&lt;br /&gt;
) ON [PRIMARY]&lt;br /&gt;
&lt;br /&gt;
The TxCode field identifies what type of transaction is taking place.  If the transaction is an AddressServer report, the TxCode will be 'AV'; if the transaction is a Location report, the TxCode will be 'L'; and so forth. The ResponseText field holds the XML document provided by the remote service (if any).  The CompanyId field holds a unique identifier within our systems, usually a quote number or a policy number.&lt;br /&gt;
&lt;br /&gt;
The XML in the AddressServer reports identifies the algorithm used for address matching and the algorithm used for "geocoding" (determining the latitude and longitude of an address).  Recently I had to determine the frequency of the various AddressServer algorithms reported within a certain time period.  My first inclination was to write a query that operated on every AddressServer report within the desired range, like so:&lt;br /&gt;
&lt;br /&gt;
Select count(*) from dbo.[Request]&lt;br /&gt;
where requestid between 1000000 and 1100000&lt;br /&gt;
and txcode = 'AV'&lt;br /&gt;
and (charindex('MatchLevel_StreetInCity', ResponseText) &amp;gt; 0)&lt;br /&gt;
and (charindex('GeocodeMatch="Postal"', ResponseText) &amp;gt; 0)&lt;br /&gt;
&lt;br /&gt;
However, I realized that this query did not operate over the correct set of records, because for each unique CompanyId, our gateway might order several AddressServer reports--and the only report we were interested in was the final one ordered.  So I needed to restructure the query so it would not operate on all the 'AV' reports, but instead only on the final report for a particular CompanyId.&lt;br /&gt;
&lt;br /&gt;
I was able to come up with a clever query, but before I share it with you, I want to give my readers the opportunity to figure out the correct query for themselves.  If you want to show the world you're a SQL guru, leave a comment with a query that operates on the correct set of records.  For those who do not have the time or inclination to work on this puzzler,  I will share my own query in a comment to this post on Monday afternoon, December 17, 2007.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;EDIT: 17 December, 2007  - Since no one wanted to claim SQL guru status by leaving a comment, I will provide my answer in the post, where I can apply helpful formatting.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The fundamental task is this: for each CompanyId you have to find the record (with txCode 'AV') that is most recent.  Since the RequestId field is auto-incremented, the most recent record is also the one with the greatest RequestId.  Thus we can reduce our set of records by restricting it to those with the Max(RequestId) for any particular CompanyId:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;em&gt;First draft of T-SQL solution:&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Select count(*) &lt;br /&gt;
 from dbo.[Request]&lt;br /&gt;
&lt;strong&gt; where requestid in&lt;br /&gt;
   (select max(requestId) from dbo.[Request]&lt;br /&gt;
    where requestid between 1000000 and 1100000&lt;br /&gt;
    and txcode = 'AV'&lt;br /&gt;
    group by companyId)&lt;br /&gt;
&lt;/strong&gt; and (charindex('MatchLevel_StreetInCity', ResponseText) &amp;gt; 0)&lt;br /&gt;
 and (charindex('GeocodeMatch="Postal"', ResponseText) &amp;gt; 0) &lt;/p&gt;
&lt;p&gt;Notice that once we have sub-selected the correct request IDs based on TxCode and requestId range, we can remove those conditions from the (outer) select's predicate.  &lt;/p&gt;
&lt;p&gt;However, we still have a problem: what if the last 'AV' transaction for a particular companyId is not within the range?  For example, the rquestId for the first 'AV' record for a companyId might be 1099999, and the final one might be 1100001 (which is not between 100000 and 1100000).  How do we deal with this problem?  It's time to use a clause which is probably used far too little in the world of SQL programming--the "having" clause:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;em&gt;Second draft of T-SQL solution:&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Select count(*) &lt;br /&gt;
 from dbo.[Request]&lt;br /&gt;
 where requestid in&lt;br /&gt;
   (select max(requestId) from dbo.[Request]&lt;br /&gt;
    where requestid between 1000000 and 1100000&lt;br /&gt;
    and txcode = 'AV'&lt;br /&gt;
    group by companyId&lt;br /&gt;
    &lt;strong&gt;having count(*) &amp;gt; 1&lt;/strong&gt;&lt;font face="Arial"&gt;)&lt;br /&gt;
 and (charindex('MatchLevel_StreetInCity', ResponseText) &amp;gt; 0)&lt;br /&gt;
 and (charindex('GeocodeMatch="Postal"', ResponseText) &amp;gt; 0)&lt;br /&gt;
&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;The having clause guarantees that the "group by" clause will exclude any records for a companyId that has only a single 'AV' record within the range.&lt;/p&gt;
&lt;p&gt;Of course, if it is possible for a companyId to have 3 or more 'AV' records, 2 of which are within the range and one which falls after the range.  Since the last one (outside the range) is the one we would need--and we are not going to be able to include it unless we do not provide an upper bound (not the intention of this query), we have to add one final condition:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;em&gt;Final T-SQL solution:&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Select count(*) &lt;br /&gt;
 from dbo.[Request]&lt;br /&gt;
 where requestid in&lt;br /&gt;
   (select max(requestId) from dbo.[Request]&lt;br /&gt;
    where requestid between 1000000 and 1100000&lt;br /&gt;
    and txcode = 'AV'&lt;br /&gt;
    group by companyId&lt;br /&gt;
    having count(*) &amp;gt; 1)&lt;br /&gt;
&lt;strong&gt;and requestid not in (select requestId from dbo.[Request] where txcode = 'AV' and requestid &amp;gt; 1100000)&lt;br /&gt;
&lt;/strong&gt; and (charindex('MatchLevel_StreetInCity', ResponseText) &amp;gt; 0)&lt;br /&gt;
 and (charindex('GeocodeMatch="Postal"', ResponseText) &amp;gt; 0)&lt;/p&gt;
&lt;p&gt;Now we have excluded any 'AV' records which are not truly the final one for a particular companyId.  The task is finished!  &lt;/p&gt;
&lt;p&gt;But why does it have to be so hard? :)  Comments welcome!&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=117756"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=117756" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/117756.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2007/12/15/117756.aspx</guid>
            <pubDate>Sat, 15 Dec 2007 23:20:24 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/117756.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2007/12/15/117756.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/117756.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/117756.aspx</trackback:ping>
        </item>
        <item>
            <title>Opportunistic SOA (or How to Make Your Cool New Code Survive the Hype Cycle)</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2007/04/10/111342.aspx</link>
            <description>&lt;P&gt;Robin Harris just &lt;A href="http://blogs.zdnet.com/storage/?p=118&amp;amp;tag=nl.e539"&gt;posted&lt;/A&gt; a near-heresy on his ZDNet blog: he thinks that SOA (Service-Oriented Architecture) is overhyped, and will die a painful death as businesses discover the difficulties in monetizing it and making it reliable.&amp;nbsp; Come on now--hype, in our industry?&amp;nbsp; Please!&lt;/P&gt;
&lt;P&gt;Actually...I agree that we are indeed well into a hype cycle regarding web services, and we would do well to make sure that SOA doesn't become the next "Golden Hammer."&amp;nbsp; This is a term coined by Brown, Malveau et al. in their terrific book, &lt;A href="http://www.amazon.com/AntiPatterns-Refactoring-Software-Architectures-Projects/dp/0471197130/ref=pd_bbs_1/002-0887647-3737634?ie=UTF8&amp;amp;s=books&amp;amp;qid=1176233138&amp;amp;sr=8-1"&gt;Anti-Patterns&lt;/A&gt;, and it refers to the idea that, to a little boy with a hammer, everything is a nail.&amp;nbsp; And likewise, to an organization that has drunk deeply of the SOA hype, every new software initiative&amp;nbsp;can be&amp;nbsp;an opportunity to create a web service.&lt;/P&gt;
&lt;P&gt;Yet certain types of problems are best solved with web services, provided that you recognize the opportunities and take advantage of them.&amp;nbsp; Implementing services as the right opportunities arise is what I call "Opportunistic SOA."&amp;nbsp; &lt;/P&gt;
&lt;P&gt;Whenever one of our lines of business asks for some new functionality, we ask ourselves, "Will any other lines of business want this functionality as well?"&amp;nbsp; If the answer is "yes" or "most likely," we typically invest the 25% extra effort needed to implement the functionality as a service.&amp;nbsp; Then when the next LOB asks for the functionality, we get to reap the reward of a vastly simpler and faster implementation.&amp;nbsp; And when a third LOB wants it, we're laughing our way to the bank.&lt;/P&gt;
&lt;P&gt;Obviously, there are some situations where only a web service will do.&amp;nbsp; If you want to access some functionality across the internet, you're going to use a web service, period, end of story.&amp;nbsp; Also, if systems on disparate platforms require the same functionality, you will probably&amp;nbsp;write a web service, because the alternative is to write the same functionality multiple times in multiple languages for multiple platforms.&lt;/P&gt;
&lt;P&gt;When systems on the same platform on the corporate network need the same functionality, though, there are tried-and-true approaches that compete with web services.&amp;nbsp; In this situation,&amp;nbsp;how do you distinguish a good SOA opportunity from an opportunity to make your users' lives harder while playing with a new technology?&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;* A good web service hides the complexity of interacting with another system.&lt;/STRONG&gt;&amp;nbsp; This is why&amp;nbsp;our shop does&amp;nbsp;not use web services to make database calls, even though it involves inter-process communication.&amp;nbsp; A database has so many possible interfaces (stored procs, table structures, etc.) that inserting an additional web service layer between the client and the server usually does not simplify anything.&amp;nbsp; The only time a web service interface might make sense for a data layer is when the data has to be transported over HTTP due to network considerations.&amp;nbsp; If you're inside the firewall, in other words, it's hard to justify writing a web service data layer.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;* A good web service simplifies the management of inter-process interactions by allowing all updates to occur in a single place--namely, the web service.&lt;/STRONG&gt;&amp;nbsp; Remember, you can simplify an interface to a complex system with the &lt;A href="http://www.dofactory.com/Patterns/PatternFacade.aspx"&gt;facade design pattern&lt;/A&gt;, which relies on a facade class to handle the complexity of interacting with some other system.&amp;nbsp; &lt;EM&gt;If it does not make sense to manage the interaction via a single system&amp;nbsp;at a single point in your network, you should not write a web service&lt;/EM&gt;; you should write a facade class library instead and compile it into your codebase.&lt;/P&gt;
&lt;P&gt;Our shop has used web services to provide interfaces to services from outside vendors.&amp;nbsp; For example, some of our homeowners insurance lines acquire outside data regarding fire protection class, building code effectiveness grading scale, and distance to shore before underwriting a particular home.&amp;nbsp; The vendor's XML interface is quite complex, so we use an internal web service to simplify the interface.&amp;nbsp; Using a web service (instead of a facade class) also allows us to manage all the vendor updates and the configuration elements in a single place.&lt;/P&gt;
&lt;P&gt;Another situation that was ripe for a web service was our need to convert PCL-formatted documents from our legacy system into PDF format for a web application.&amp;nbsp; We got a server license for a conversion component, wrapped it in a web service interface, and were able to perform the necessary conversion from any system on any platform.&amp;nbsp; And recently, when we discovered that the vendor component was not as reliable as we had hoped, we rejoiced that we did not have to re-write and re-compile code in a dozen different places to use a new vendor's (server-licensed) component.&amp;nbsp; We have only one system whose source needs to change--the web service.&amp;nbsp; We are planning to change the web service implementation in the near future, and all the client systems will get the benefit of the improved reliability without blinking an eyelash.&lt;/P&gt;
&lt;P&gt;As is often the case with a new architecture, there is a sweet spot between the extremes of SOA hype (on the one hand) and total rejection of change (on the other).&amp;nbsp; I hope this article will help you find it.&lt;/P&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=111342"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=111342" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/111342.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2007/04/10/111342.aspx</guid>
            <pubDate>Wed, 11 Apr 2007 01:55:00 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/111342.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2007/04/10/111342.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/111342.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/111342.aspx</trackback:ping>
        </item>
    </channel>
</rss>