Package 'Microsoft.VisualStudio.Xaml' has failed to load properly

Tuesday, 17 March 2009 05:33 by Admin

Today I finally got around to delving back into some WPF development only to be greeted with the following error when opening Visual Studio 2008.

Package 'Microsoft.VisualStudio.Xaml' has failed to load properly ( GUID = {E58C2A8B-BCC4-4559-AD59-D62EB6D58A22} ). Please contact package vendor for assistance. Application restart is recommended, due to possible environment corruption. Would you like to disable loading this package in the future? You may use 'devenv /resetskippkgs' to re-enable package loading.

At first I tried resetting my environment settings to no avail. Then I uninstalled/reinstalled VS 2008 with no love there either. Then I remembered that I hadn't yet installed VS 2008 SP1 so I did that and problem solved. So if you are receiving this error you may want to install/reinstall SP1 first and see if it solves your problem.

 

 

Categories:  
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

LINQ to SQL Outer Joins

Tuesday, 21 October 2008 11:32 by slanford

Transitioning from standard SQL to LINQ to SQL is in many ways very intuitive, both conceptually and syntactically. Some routine tasks are however decidedly unintuitive, at least in comparison to standard SQL. One such task is constructing outer joins. As you will soon see the LINQ to SQL syntax for performing outer joins is far less intuitive for most people than the OUTER JOIN keyword syntax of standard SQL. Hopefully in a future version of LINQ to SQL Microsoft will give us an OUTER JOIN operator but until then here are four ways to do outer joins in LINQ to SQL.

Suppose we have a Patients table and a Claims table where a patient can have zero or more claims and we want to return all patients, regardless of whether they have a claim, and any claim information we have if any. This is of course very simple to do in standard SQL with an outer join. Let's see how to accomplish this with LINQ to SQL.

Method 1: Using a Group Join with DefaultIfEmpty()

            MedicalDataContext db = new MedicalDataContext();
            var query = (  from p in db.Patients
                           join c in db.Claims on p.UnqPatientId equals c.PatientId into claims
                           from c in claims.DefaultIfEmpty()
                         select new { Patient = p, Claim = c });
 
            foreach (var item in query)
            {
                System.Diagnostics.Debug.WriteLine(String.Format("Account {0}: #{1}.", item.Patient.AccountNumber, (item.Claim == null ? String.Empty : item.Claim.ClaimNumber)));
            }

A group join is a join that uses the into operator to coalesce parent and child information into a single hierarchical collection so you end up with parent information appearing only once in the collection as opposed to flattened out into multiple instances as you would normally get. To convert this group join into a left join we simply add another from clause to select from the group and apply the DefaultIfEmpty() method to force the inclusion of null child references where parents have no children. Note that this method of performing an outer join produces a flattened result.

Method 2:  Using GroupJoin()

            MedicalDataContext db = new MedicalDataContext();
            var query = db.Patients.GroupJoin(db.Claims,
                                              p => p.UnqPatientId,
                                              c => c.PatientId,
                                              (Patient, Claims) => new { Patient, Claims });
 
            foreach (var item in query)
            {
                System.Diagnostics.Debug.WriteLine(String.Format("Account {0} has {1} claims.", item.Patient.AccountNumber, item.Claims.Count()));
            }

In the above example I am using the GroupJoin() method to define my outer join. The first parameter specifies the inner table to join on, the second parameter specifies the key column of the outer table, the third parameter specifies the key column of the inner table and finally the fourth parameter specifies what to return. Note that this method of performing an outer join produces a hierarchical result.

 Method 3:  Project Directly Into Object Hierarchy

            MedicalDataContext db = new MedicalDataContext();
            var query = (  from p in db.Patients
                         select new
                         {
                             Patient = p,
                             Claims = db.Claims.Where(c => c.PatientId == p.UnqPatientId)
                         });
 
            foreach (var item in query)
            {
                System.Diagnostics.Debug.WriteLine(String.Format("Account {0} has {1} claims.", item.Patient.AccountNumber, item.Claims.Count()));
            }

In this example I am using a Lambda Expression to project the claim data directly into the object hierarchy. Note that this method of performing an outer join produces a hierarchical result.

 Method 4:  Use Association Property

            MedicalDataContext db = new MedicalDataContext();
            var query = (  from p in db.Patients
                         select new
                         {
                             Patient = p,
                             Claims = p.Claims
                         });
 
            foreach (var item in query)
            {
                System.Diagnostics.Debug.WriteLine(String.Format("Account {0} has {1} claims.", item.Patient.AccountNumber, item.Claims.Count()));
            }

In this example I am using the Claims association property of the Patient object to project the claim data into the object hierarchy. Note that this method of performing an outer join produces a hierarchical result.

Categories:   LINQ to SQL
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Returning LINQ to SQL Entities from a WCF Service

Monday, 6 October 2008 10:50 by slanford

When returning LINQ to SQL entities from a WCF service using the default properties for your LINQ to SQL entity model you will almost certainly encounter an immediate error when you attempt to call a service method. In fact, the only scenario I can think of in which you may not receive out-of-the box errors is if your model contains no relationships. For example, consider the following entity model and service method.

NOTE: The following scenario will only work if running service pack 1 for the .NET Framework 3.5 because the LINQ to SQL entity model is set to the default serialization mode of "None". This will work with SP1 because SP1 adds implicit DataContractSerializer support for Plain Old CLR Objects (POCO) by serializing public fields and read/write properties. Later in this post you'll see how to change the serialization mode to remove this restriction as well as solve the issue being presented.

LINQ to SQL Entity Model  (with Serialization Mode = "None")

        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            return employees;
        }

WCF Service Method 

Now at this point if I call GetEmployees() from a client I would be greeted with a "The underlying connection was closed: The connection was closed unexpectedly." WebException. Why is this? The obvious answer is some exception occurred in the service method...but what? My first instinct was that it had something to do with the deferred loading mechanism of LINQ to SQL since the above scenario works without error if I simply remove the entity model relationship. Regardless of the cause I knew that the error must be happening during serialization since I could step through the service method without error. So as a quick way to view the underlying exception without having to enable tracing on the server I modified the service method to serialize my result inline.

        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            //
            //Serialize the result to debug error
            //
            try
            {
                DataContractSerializer dcs = new DataContractSerializer(typeof(List<Data.Employee>));
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
                {
                    dcs.WriteObject(ms, employees);
                }
            }
            catch (Exception ex)
            {
 
            }
 
            return employees;
        }

Modifed Debug Version of WCF Service Method 

Now when stepping through the service method I was able to see the actual exception which was not what I initally expected. The exception was "Object graph for type ... contains cycles and cannot be serialized if reference tracking is disabled." The cause of this error is due to circular references in the entity objects as a result of the defined relationship; Employee objects have a reference to one or more EmployeeTerritory objects and each EmployeeTerritory object has a reference back to its parent Employee object which can't be represented when serialized unless we enable the preservation of theses object references during serialization. There are two ways to enable object reference preservation for our LINQ to SQL classes.

1. The first way is to instruct the DataContractSerializer to preserve object references by setting the preserveObjectReferences of the appropriate constructor overload to true. For details on how to do this in WCF see Sowmy Srinivasan's post here. Note that even though this seems to work in my limited testing it is not supported. To quote the MSDN documentation here..."Unidirectional serialization is the only type of serialization supported by LINQ to SQL."

2. That brings us to the second and recommended way of enabling object reference preservation; change the Serialization Mode for the entity model to "Unidirectional" in Visual Studio.

With unidirectional serialization, as the name implies, bidirectional associations are not maintained. That is, the serialized object will only maintain a one-way association to avoid a circular reference. By convention, the property on the parent side of the association is serialized and the property on the child side of the association is not. So in my example the Employee.EmployeeTerritories[] property would be serialized but the EmployeeTerritory.Employee property would not.

There is however an unintended side-effect of changing the Serialization Mode. That is related data is not automatically fetched as the object graph is walked upon serialization. So in this example if we simply change the serialization mode to "Unidirectional" and do nothing else, what we get on the client side is a list of Employee objects with a null EmployeeTerritories[] property. Fortunately there is a relatively easy way to solve this by instructing LINQ to SQL to retrieve the EmployeeTerritory data along with the Employee data. To do this we need to specify the DataLoadOptions for the DataContext. Here is the modified WCF service method.

 
        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
            System.Data.Linq.DataLoadOptions dlo = new System.Data.Linq.DataLoadOptions();
            dlo.LoadWith<Data.Employee>(e => e.EmployeeTerritories);
            context.LoadOptions = dlo;
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            return employees;
        }
Modified WCF Service Method To Include EmployeeTerritory Data In Results 

Now when we call this service method we get both Employee and EmployeeTerritory data at the client as expected.

Categories:   LINQ to SQL | WCF
Actions:   E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed

Windows Media Player Sharing - Exception from HRESULT: 0x00403C9

Monday, 29 September 2008 12:11 by slanford

One of my side projects is working on a Windows Home Server (WHS) add-in to allow remote access to all of my music and photos. I recently upgraded my WHS server to Windows Media Player 11 (WMP11) so that I could access photos on the server from my new i-mate Momento 70 wireless digital picture frame which only supports WMP11. Note that WMP11 is not officially supported on WHS but it seems to work just fine once you trick it into installing.

After doing the upgrade I immediately ran my add-in application to see if it still worked and of course...it didn't. The application started throwing the following error:

 Exception from HRESULT: 0x00403C9

It turns out that this is an "Access Denied" error due to the user I was running the app under not being allowed access to my shared media. To solve the problem I simply had to go to Options->Library->Configure Sharing in WMP11 and allow access to the "Other users of this PC" group.

Godaddy SMTP

Friday, 26 September 2008 09:18 by slanford

If you have a shared hosting plan with Godaddy you can use the following settings to send email.

Server: relay-hosting.secureserver.net
Port: 25
Login: Not required
From Address: youremail@yourdomain (note that emails must be sent from an email address from your domain)

Categories:   Godaddy
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Codeexperiment Has Moved

Thursday, 25 September 2008 08:35 by slanford

It was recently brought to my attention that I had credits available for free "Economy Hosting" at Godaddy.com where I purchase my domains. Now I assumed that this would only include Linux based PHP/MySQL hosting which wouldn't do me any good. But to my great surprise this free hosting includes ASP.NET hosting in addition to an MS SQL database. So now I can move my sites off of my limited upstream home server onto a host with greater reliability and bandwidth. So all I have to do is transfer my database and files to Godaddy and I'm done...easy right? Well, rarely is anything ever that easy.

The first problem I ran into was while trying to migrate the CommunityServer database. Godaddy supports the Microsoft Database Publishing Wizard (DPW) which is great except that it fails when trying to publish tables with large row sizes. In particular it kept failing when attempting to publish the cs_PostAttachments table which contains some relatively large files. I'm still not sure why since the only error returned was "Internal Error", but I'm assuming it is due to a timeout from the Godaddy webservice. So instead of using the DPW to transfer that one table I thought OK...I'll just script the table and run it via Godaddy's online Query Analyzer tool. Strike two...the query analyzer tool has a 2.5MB size limit on uploaded scripts and simply pasting the script into the window caused a timeout error on postback. Sigh....

Not being one to give up easily I decided to just forget about trying to migrate the table and re-upload the attachments via the CommunityServer interface.  Maddeningly, the problem then became one of ASP.NET configuration. I had initially configured IIS7 for ASP.NET 2.0, which is how my home server is configured. But this resulted in an "Operation could destabilize the runtime" exception. I'm not sure why this exception is thrown. Granted I'm running CommunityServer 2.0 with the ASP.NET 1.1 compiled assemblies but I'm running it fine on my home server with IIS configured for ASP.NET 2.0. The only difference that I can discern is that my home server is running IIS6 and the app is running with a High trust level whereas on Godaddy it is running IIS7 with a Medium trust level. Even changing the IIS7 pipeline mode from Integrated to Classic didn't help so I'm guessing it is some security issue with IIS7, the trust level or a combination of the two.

In the end I decided not to waste any more time trying to get CommunityServer 2.0 running and opted instead to switch to a different blog server application. So after several frustrating hours of trying to migrate CommunityServer and giving up, this site is now running on BlogEngine.NET. Not only was BlogEngine a breeze to install it is also very easy to configure and theme. Even better is that it doesn't require a database backend so doing site backups is as simple as zipping the folder.

 

 

Categories:   BlogEngine.NET | Godaddy
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Silverlight 2.0 Beta 1 Gridsplitter Bug

Monday, 17 March 2008 13:45 by slanford

There is a bug in Silverlight 2.0 Beta 1 which causes the browser to crash when you attempt to move a Gridsplitter with ShowPreview="True" and is defined in a Grid without explicitly defining both row(s) and column(s). One solution is to simply set ShowPreview="False". However, the best solution is to simply make sure that you have explicitly defined the row(s) and columns(s) for your Grid. Below are examples that illustrate the problem and solution.

<!-- This will crash because no explicit row is defined-->

<Grid Background="White" Width="200">

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

        <ColumnDefinition Width="*" />

    </Grid.ColumnDefinitions>

 

    <GridSplitter Grid.Column="1"

                  HorizontalAlignment="Center"

                  VerticalAlignment="Stretch"

                  ShowsPreview="True"

                  Background="Black"

                  Width="2"/>

</Grid>

 

<!-- This will work because ShowPreview is set to False -->

<Grid Background="White" Width="200">

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

        <ColumnDefinition Width="*" />

    </Grid.ColumnDefinitions>

 

    <GridSplitter Grid.Column="1"

                  HorizontalAlignment="Center"

                  VerticalAlignment="Stretch"

                  ShowsPreview="False"

                  Background="Black"

                  Width="2"/>

</Grid>

 

<!-- This will work because row/columns are defined explicitly -->

<Grid Background="White" Width="200">

    <Grid.RowDefinitions>

        <RowDefinition Height="*" />

    </Grid.RowDefinitions>

 

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

        <ColumnDefinition Width="*" />

    </Grid.ColumnDefinitions>

 

    <GridSplitter Grid.Column="1"

                  HorizontalAlignment="Center"

                  VerticalAlignment="Stretch"

                  ShowsPreview="True"

                  Background="Black"

                  Width="2"/>

</Grid>




 
Categories:   Silverlight 2.0
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Debugging XBAP/WCF Applications

Tuesday, 11 March 2008 13:52 by slanford

I recently began getting my feet wet with the WPF and WCF frameworks. As with any new development technology I am learning I like to actually build something useful. Currently I am using the WebGuide Windows Home Server (WHS) plugin for remote access to my media collection. While this is a decent plugin, I thought it would be cool to try and write a really slick WPF front-end with some eye candy. To this end I decided to develop my own WHS media plugin implemented as a XAML browser application (XBAP) that communicates with the server via a WCF service. As of .NET 3.5, WCF communication from a partial trust application is now possible with some limitations (http://msdn2.microsoft.com/en-us/library/bb412186.aspx).

Almost immediately I ran into a problem that prevented me from debugging the application in Visual Studio 2008. Whenever the application attempted to connect to the WCF service I received the following error:

Request for the permission of type 'System.Net.WebPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.

The possible reasons for this error are numerous but in this case it was because the default Debug->Start Action for an XBAP project is "Start browser in URL", where the URL simply points to the .xbap file on disk (e.g. C:\projects\myproject\bin\debug\myproject.xbap). For a partial trust XBAP application to communicate with a WCF service it must be deployed from the same domain and port as the WCF service. The way to solve this dilemma is to fake the XBAP URL by starting the XBAP application with PresentationHost.exe and supplying the XBAP URL (copied from the "Start browser with URL" value) via the debug parameter and the WCF service URL via the debugSecurityZoneURL parameter. For example the debugging options for your XBAP project should look something like this:

Start Action->Start external program = %windir%system32\PresentationHost.exe
Start Options->Command line arguments = -debug "c:\projects\myproject\bin\debug\MyProject.xbap" -debugSecurityZoneUrl "http://localhost:2022"

Categories:   .NET 3.5 | XBAP
Actions:   E-mail | Permalink | Comments (2) | Comment RSSRSS comment feed

Convert Visual Studio 2005 Code Coverage Results to XML

Saturday, 26 August 2006 07:20 by slanford

One of the great new features in Visual Studio 2005 Team Suite, Team Edition for Developers or Team Edition for Testers is the unit testing and code coverage analysis. One limitation however is that the code coverage results are output to a binary file making it useless for reporting in 3rd party tools. You can export the results to XML from Visual Studio but I needed an easy way to automate the conversion to XML as part of an automated build process using CruiseControl.NET. Having the data in XML makes displaying the results in the CruiseControl.NET dashboard a piece of cake.

After doing a bit of research of I came across this MSDN article that gave me the solution I was after. The trick is to make use of the CoverageInfo class contained in the Microsoft.VisualStudio.Coverage.Analysis.dll assembly to convert the coverage data file to a Dataset which is then easily convertible to XML.

//set symbols/exe paths

CoverageInfoManager.SymPath = SymbolsPath;

CoverageInfoManager.ExePath = ExePath;

 

//load the specified code coverage data file

CoverageInfo cinfo = CoverageInfoManager.CreateInfoFromFile(CoverageDataFile);

 

//Generate a dataset from the code coverage data file

CoverageDS cdata = cinfo.BuildDataSet(null);

From the code sample above you can see that the first thing we need to do is specify the location of the symbol file and exe file. The reason this is necessary is because the coverage file is generated in a subfolder different than the binary and symbol file which are required for conversion. If these two paths aren't set you'll see and error such as this:

Error when creating coverage info: Error loading symbol file. Symbol and binary files should be in the same folder as the coverage file or on the symbol path:

Next we create a CoverageInfo object from our coverage data file, convert it to a CoverageDS Dataset and finally save it to disk as XML.

Complete source code for the console application I created can be found in the attachment below. Note that to compile the project you will need either Visual Studio 2005 Team Suite, Team Edition for Developers or Team Edition for Testers installed. The Microsoft.VisualStudio.Coverage.Analysis.dll assembly referenced by the project is typically located in  C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies.

CodeCoverageConverter.zip (26.66 kb)

Categories:   .NET 2.0
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Implementing Transactional TableAdapters

Monday, 17 July 2006 11:06 by slanford

Almost everyone who uses the new TableAdapter functionality in Visual Studio 2005 (note that I did not write .NET 2.0 as you will find no such thing in the framework) will eventually have the need for transaction support. The basic problem is that TableAdapters do not expose the underlying Command objects, as a result you cannot set their Transaction properties in order to enlist them in a transaction.

Fortunately we can derive our TableAdapters from a custom base class which will allow us to extend the out of the box implementation and provide the functionality we need. The first thing we need to do is define the interface our base class will implement.

public interface IExtendTableAdapter

{

   /// <summary>

   /// Returns the DataAdapter for the TableAdapter

   /// </summary>

   AdapterDecorator DataAdapter { get; }

 

   /// <summary>

   /// Gets or sets the DbConnection for the TableAdapter.

   /// </summary>

   System.Data.IDbConnection DbConnection { get; set;}

}

The purpose of the DataAdapter property is to expose and extend the underlying DataAdapter to include transaction support. The DbConnection property allows us to set the Connection object used by our transaction.

Next we define the AdapterDecorator class which exposes the underlying DataAdapter and provides a method to enlist the TableAdapter in a transaction.

   public class AdapterDecorator

   {

      #region Properties

      private System.Data.Common.DbDataAdapter _adapter;

      /// <summary>

      /// Gets or sets the DataAdapter for the TableAdapter.

      /// </summary>

      public System.Data.Common.DbDataAdapter Adapter

      {

         get { return _adapter; }

         set { _adapter = value; }

      }

 

      private System.Data.Common.DbCommand[] _commands;

      /// <summary>

      /// Gets or sets the non-DataAdapter Command objects.

      /// </summary>

      public System.Data.Common.DbCommand[] Commands

      {

         get { return _commands; }

         set { _commands = value; }

      }

      #endregion Properties

 

      #region Constructors

      public AdapterDecorator(System.Data.Common.DbDataAdapter adapter)

      {

         _adapter = adapter;

      }

 

      public AdapterDecorator(System.Data.Common.DbDataAdapter adapter, System.Data.Common.DbCommand[] commands)

      {

         _adapter = adapter;

         _commands = commands;

      }

      #endregion Constructors

 

      #region Public Methods

      public void EnlistTransaction(System.Data.Common.DbTransaction transaction)

      {

         //set Adapter Command object transactions

         if (_adapter != null)

         {

            if (_adapter.InsertCommand != null)

            {

               _adapter.InsertCommand.Connection = transaction.Connection;

               _adapter.InsertCommand.Transaction = transaction;

            }

            if (_adapter.UpdateCommand != null)

            {

               _adapter.UpdateCommand.Connection = transaction.Connection;

               _adapter.UpdateCommand.Transaction = transaction;

            }

            if (_adapter.DeleteCommand != null)

            {

               _adapter.DeleteCommand.Connection = transaction.Connection;

               _adapter.DeleteCommand.Transaction = transaction;

            }

         }

 

         //set non-Adapter Command object transactions

         if (_commands != null)

         {

            for (int i = 0; i < this._commands.Length; i++)

            {

               _commands[i].Connection = transaction.Connection;

               _commands[i].Transaction = transaction;

            }

         }

      }

      #endregion Public Methods

   }

Now we define the TableAdapterBase class which will be the base class from which our TableAdapters will be derived.

public abstract class TableAdapterBase : System.ComponentModel.Component, IExtendTableAdapter

   {

      #region Private Fields

      AdapterDecorator adapterDecorator;  //The extended DataAdapter

      IDbConnection connection;           //The database connection

      #endregion Private Fields

 

      #region IExtendTableAdapter Members

 

      public AdapterDecorator DataAdapter

      {

         get

         {

            //========================================================================================

            // Use Reflection to get the private Adapter and CommandCollection properties defined by

            // the Designer generated code.

            //========================================================================================

 

            if (this.adapterDecorator == null)

            {

               System.Data.Common.DbDataAdapter adapter = null;

               System.Data.Common.DbCommand[] commands = null;

 

               Type t = this.GetType();

               //If parent type is not TableAdapterBase then we need to reflect the properties

               //from the parent type. Otherwise the "Adapter" property will not be found since

               //it is private.

               Type parentT = t.BaseType;

               if (!(parentT.FullName == "TransactionalTableAdapters.TableAdapterBase"))

                  t = parentT;

 

               //Get the Adapter property defined by the TableAdapter's designer generated code

               BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

               PropertyInfo pi = t.GetProperty("Adapter", bf);

               if (pi != null)

                  adapter = pi.GetValue(this, null) as System.Data.Common.DbDataAdapter;

 

               //Get the CommandCollection property defined by the TableAdapter's designer generated code

               pi = t.GetProperty("CommandCollection", bf);

               if (pi != null)

                  commands = pi.GetValue(this, null) as System.Data.Common.DbCommand[];

 

               //Create the AdapterDecorator object

               this.adapterDecorator = new AdapterDecorator(adapter, commands);

            }

 

            return this.adapterDecorator;

         }

      }

 

      public IDbConnection DbConnection

      {

         get

         {

            //=================================================================

            // Use Reflection to get the Connection property defined by the

            // Designer generated code.

            //=================================================================

 

            if (this.connection == null)

            {

               Type t = this.GetType();

               BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

 

               //Get the Connection property.

               PropertyInfo pi = t.GetProperty("Connection", bf);

               if (pi != null)

                  connection = pi.GetValue(this, null) as IDbConnection;

            }

 

            return this.connection;

         }

         set

         {

            this.connection = value;

         }

      }

 

      #endregion

   }

Finally, we define a TransactionHelper class to make it easy to enlist our TableAdapters in a transaction.

public sealed class TransactionHelper : IDisposable

   {

      #region Private Fields

      DbConnection _connection;     //The database connection

      DbTransaction _tran = null;         //The database transaction

      #endregion Private Fields

 

      #region Public Methods

      /// <summary>

      /// Enlists the specified table adapter in the current transaction.

      /// </summary>

      /// <param name="tableAdapter">The table adapter to enlist. The table adapter must implement <see cref="IExtendTableAdapter"/>.</param>

      /// <remarks>

      /// </remarks>

      public void EnlistParticipant(System.ComponentModel.Component tableAdapter)

      {

         //Make sure the TableAdapter implements IExtendTableAdapter which is required for transaction support

         if (!(tableAdapter is IExtendTableAdapter))

            throw new Exception("Type " + tableAdapter.GetType().Name + " is invalid because it does not implement IExtendTableAdapter.");

 

         IExtendTableAdapter ta = tableAdapter as IExtendTableAdapter;

 

         //We need to share the same connection across TableAdapters for them to participate in the same

         //transaction so get connection from the first TableAdapter we encounter and use it for all

         //others.

         if (_connection == null)

         {

            _connection = ta.DbConnection as DbConnection;  //get the connection

            _connection.Open();                                         //open the connection

            _tran = _connection.BeginTransaction();               //create a transaction

         }

         else //we already have our connection so change the connection of the TableAdapter to use it

         {

            ta.DbConnection = _connection;

         }

 

         //Enslist the TableAdapter in the transaction

         ta.DataAdapter.EnlistTransaction(_tran);

      }

 

      /// <summary>

      /// Completes the operations and commits the transaction.

      /// </summary>

      public void Complete()

      {

         //Commit and dispose the transaction

         if (_tran != null)

         {

            _tran.Commit();

            _tran.Dispose();

            _tran = null;

         }

      }

      #endregion Public Methods

 

      #region IDisposable Members

 

      /// <summary>

      /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

      /// </summary>

      public void Dispose()

      {

         //Rollback and dispose transaction if still present

         if (_tran != null)

         {

            _tran.Rollback();

            _tran.Dispose();

         }

 

         //close the connection

         if ((_connection != null) && (_connection.State != ConnectionState.Closed))

            _connection.Close();

      }

 

      #endregion

   }

This class is used very much like the TransactionScope class with the exception that we must explicitly enlist our TableAdapters in the transaction by calling the EnlistParticipant() method. For example:

using (TransactionHelper th = new TransactionHelper())

{

   //enlist TableAdapters in our transaction

   NorthwindDataSetTableAdapters.OrdersTableAdapter ordersTA =

      new TransactionalTableAdapters.NorthwindDataSetTableAdapters.OrdersTableAdapter();

   th.EnlistParticipant(ordersTA);

   NorthwindDataSetTableAdapters.Order_DetailsTableAdapter detailsTA =

      new TransactionalTableAdapters.NorthwindDataSetTableAdapters.Order_DetailsTableAdapter();

   th.EnlistParticipant(detailsTA);

 

   //Update the Orders table

   ordersTA.Update(ordersRow);

   //Get the order id

   int orderID = ordersRow.OrderID;

                       

   //Insert Order_Details row

   detailsTA.Insert(orderID, 1, Convert.ToDecimal(3.39), 10, 0f);

 

   //Commit the transaction

   th.Complete();

}

Complete source code for this post can be found in the attachment below.

TransactionalTableAdapters.zip (29.29 kb)

Categories:   .NET 2.0
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed