Finally, An example of a Multi tier architecture for Linq to Sql


Update 2/6/2008:

The original post for this is available  beneath, but the link to the original article has been removed which turns this post to a pointless rant because you cannot reference the article. Luckily Beth Massi has a very good n’tier Linq example.

Original

Anyone that is aware of my posts knows that I do whinge about the lacklustre implementability of Linq in real world scenarios. I have battled with this for some time now.

Microsoft’s response a few months back was that they were waiting to see how their customers would ‘conjure up’ a method of doing this.
I found this attitude dissatisfactory, because if you’re trying to negate the impedance mismatch between ‘objects’ and ‘data’, the ubiquitous scenario is n-tier. You cannot introduce such a feature (Linq), and not have examples of it being implemented n-tier.
I have just come across an example of implementing Linq to Sql on MSDN. The link is available here.

I have finished testing this and quite frankly, I’m very disappointed. This is not easier than datasets, but more complex, for absolutely no ROI for understanding the raised abstraction.
Validation is pain, even more painful is moving this to winforms/wpf applications, that do not have the object data source.

I keep returning to Linq because of all the noise Microsoft made. Indisputably, one is more expressive when composing queries, but that is just one small part in creating an n-tier app.

Nah, back to datasets for me, and quick. Unless a miracle happens.

Linq to SQL Compact Edition (CE)


I got asked about doing this recently, and needed somewhere to point the individual to.

Presently, unfortunately, Linq to SQL Compact Edition is unsupported. Luckily though, it is quite easy to still perform Linq queries against SQL compact using the command line utility SqlMetal. SQL Compact Edition is very handy for single user applications or demos where you want to retain a very small application imprint, but not lose functionality. All that is required is that you have the object relational mapping (ORM) file that contains the Linq to SQL classes. This in Visual Studio is the .dbml file that is generated when you elect LinqToSQLClasses in the data menu.

Open up the Visual Studio command prompt by going to

You should have the following;

C:\Program Files\Microsoft Visual Studio 9.0\VC>

Enter ‘SqlMetal’ in the command prompt and take time to explore all the options available to you.

I simply want to create a .dbml file from my database which I have in my c:\ drive. You will want to point this to wherever your .sdf file is.

Enter the following path into the Visual Studio command prompt

SqlMetal /dbml:c:\Users\MyName\Documents\Northwind.dbml "C:\Users\MyName\Documents\Visual Studio 2008\Projects\Windows Forms\Code\Northwind\Northwind Database\Northwind.sdf"

Where the above is the format; SqlMetal /dbml:northwind.dbml northwind.sdf

Note that in vista you must specify the path through the ‘Users folder’. Failure to do this will result in a file access error.

As you can see my .dbml file was copied to my documents folder. I then copied this into my project. .To access the file I reference it in the constructor of the form thus;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        NorthwindDataContext northwindDataContext;

        public Form1()
        {
            InitializeComponent();

            this.northwindDataContext = new NorthwindDataContext(Properties.Settings.Default.NorthwindConnectionString);
        }
    }
}

Windows Communication Foundation Support in Visual Studio 2008


Recently I was having major problems designing a smart client application because the architecture necessitated a n-tier (or multi tier) paradigm. This "Architecture" was non-negotiable insofar as the smart client being for a relatively successful business. The advantages of this type of architecture are well documented so feel free to Google search on the subject.

The following tutorial will take you through the steps required to create an n-tier application using Visual Studio 2008.

  1. Create a new windows application called NorthwindTraders
  2. In the same solution, add a new class library called Northwind.DataAccessLayer (Delete the Class1.cs file in this project)
  3. Add another class library called Northwind.BusinessEntities (Delete the Class1.cs file in this project)
  4. Add a WCF Service Application called WcfService1 (the default). Make sure you choose the WCF service under the web options shown below

Service

Your solution explorer should have the following four projects.

SolutionExplorer

In the DataAccessLayer project add a DataSet. To do this, make sure the DataAccessLayer project is selected in Solution Explorer, and from the main menu choose "add new data source". Make sure Database is selected and click “Next”.

Note:  I’m assuming you have downloaded and installed the Northwind database, if not you can get the ubiquitous database here.

AddNewDataSource

If you don’t already have a connection to Northwind, click the “New Connection” button and select the SQL database file

DatabaseFile

You should now be able to browse to the database, and test to make sure the connection is working

BrowsetoDB

Click on “Next”, if you get the following dialog, saying yes makes a copy of the Northwind Database to your project. I usually say  no because you ens up with dozens if not hundreds of copies of the same database in your Visual Studio Projects folders, this was you always use the same database in all your projects.

CopyDBMessage

Save the connection as “NorthwindConnectionString” and Click “Next”. Choose the “Customers” and “Orders” tables, and Change the name of the DataSet to “CustomersDataSet” and click on “Finish”.

CustomersDataSet

”Choose the default database option and click next. Select the Northwind database and then choose the customers and orders tables. Change the name of the DataSet to "CustomerOrdersDataSet" and click finish.

Double click the “CustomersDataSet” in Solution Explorer and right click on the dataset and select properties

DataSetProperties

In the properties pane you can now choose the DataSet project for the data access layer. This is a new feature in Visual Studio 2008 that allows you to separate you data access logic and your business logic into two projects, which was a real pain point in Visual Studio 2005. In this case choose the Northwind.BusinessEntities project and make sure you click “Save”.

ChooseDataBaseProject

If you double click the orders table in the DataSet designer, you should be taken into the business entities project, where you can add some validation logic.

namespace Northwind.BusinessEntities {

 

 

    public partial class CustomersDataSet {

        partial class OrdersDataTable

        {

            public override void EndInit()

            {

                base.EndInit();

                this.ColumnChanged += new System.Data.DataColumnChangeEventHandler(OrdersDataTable_ColumnChanged);

            }

 

            void OrdersDataTable_ColumnChanged(object sender, System.Data.DataColumnChangeEventArgs e)

            {

                if (e.Column.ColumnName == this.OrderDateColumn.ColumnName

                    || e.Column.ColumnName == this.ShippedDateColumn.ColumnName)

                {

                    VaidateDates((OrdersRow)e.Row);

                }

            }

            /// <summary>

            /// Simple validation to ensure that the date an order is shipped is not

            /// before it has been ordered

            /// </summary>

            /// <param name="ordersRow"></param>

            private void VaidateDates(OrdersRow ordersRow)

            {

                if (ordersRow.OrderDate > ordersRow.ShippedDate)

                {

                    ordersRow.SetColumnError(this.OrderDateColumn, "Cannot ship before it’s been ordered");

                    ordersRow.SetColumnError(this.ShippedDateColumn, "Cannot ship before it’s been ordered");

                }

                else

                {

                    ordersRow.SetColumnError(this.OrderDateColumn, "");

                    ordersRow.SetColumnError(this.ShippedDateColumn, "");

                }

            }

        }

    }

}

We can now complete the setup by adding a class into the data access layer called the NorthwindDataManager that connects to the database, and returns the tables. The NorthwindManager will typically contain all the queries that you create. If you need to know how to create queries you can have a look at this article, or do some research. You typically you change the query to GetOrders and not GetData, but some knowledge of this is assumed.

Queries

This is the code that returns a DataTable in the NorthwindDataManager. Make sure you include the Using statements at the top

 

using System;

using System.Collections.Generic;

using System.Linq;

using Northwind.BusinessEntities;

using System.Text;

using Northwind.DataAccessLayer.CustomersDataSetTableAdapters;

 

 

namespace Northwind.DataAccessLayer

{

    /// <summary>

    /// Typically this class would not return all the data, but specific queries related to a table.

    /// For the sake of brevity, we will return all the orders in the northwind database. Also try

    /// change the GetData() to GetOrders() in the OrdersTableAdapter by modifying the query in the

    /// DataSetDesigner. Knowledge of doing this is assumed.

    /// </summary>

    public class NorthwindDataManager

    {

        public static CustomersDataSet.OrdersDataTable GetOrders()

        {

            OrdersTableAdapter ordersAdapter = new OrdersTableAdapter();

            return ordersAdapter.GetData();

        }

    }

 

}

Now we will configure the service. Select the WCF service project, right click the “References” node in Solution Explorer, and go “Add References.” choose the projects tab at the top and add the DataAccessLayer and BusinessEntities projects.

AddReferences

You also need to add a reference to System.Data.DataSetExtensions so add a reference as above, but this time choose the .NET tab

DataSetExtensions

So your solution explorer should look like this

References

In the IService interface file, remove the default entries and add a contract called GetOrders (not forgetting the namespaces at the top)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using Northwind.BusinessEntities;

 

namespace WcfService1

{

    // NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.

    [ServiceContract]

    public interface IService1

    {

        [OperationContract]

        CustomersDataSet.OrdersDataTable GetOrders();

    }

}

In the Service1.scv file remove the default data and implement the IServive1 interface. A quick shortcut to do this is to right click the IService1 in Visual Studio shown below (this can save quite a lot of typing when setting up big service layers)

ImplementInterface

Add the following code (again note the namespaces above)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using Northwind.DataAccessLayer;

using Northwind.BusinessEntities;

 

namespace WcfService1

{

    // NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config and in the associated .svc file.

    public class Service1 : IService1

    {

 

        #region IService1 Members

 

        public CustomersDataSet.OrdersDataTable GetOrders()

        {

            return NorthwindDataManager.GetOrders();

        }

 

        #endregion

 

    }

}

To complete the tutorial go to the NorthwindTraders project, which typically is your client application and make sure you add a reference to the business entities project. This is important because it allows proxy reuse (another VS 2008 Key feature). You should also see here that at no stage is the data access component ever available or referenced on the client. Make sure you build the entire Solution now by clicking F5.

Right click the references node and add a service reference this time.

AddServiceReference

Click the discover button and wait till the reference is located and then click OK

ServiceReference

Note: A word of warning is that if you use third party software libraries, than you do not want their libraries included here. Be sure to click the advanced button, and choose “Reuse types in specified referenced assemblies” and omit them. Failure to do this may result in some rather nasty Visual Studio crashes.

In the app.config, as a security precaution, the client configuration we generate has a default MaxRecievedMessageSize value of 65536, meaning that an exception will be thrown at runtime if the service sends more data to the client than it expects. Since we know we will be passing large DataSets over the wire, please open up the app.config and set the element <bindings>\<wsHttpBinding>\<binding>’s maxReceivedMessageSize attribute to 5000000. Or, if you feel uncomfortable editing your service’s configuration by hand, you can open up the Microsoft Service Configuration Editor available under Visual Studio’s Tools menu.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

    <system.serviceModel>

        <bindings>

            <wsHttpBinding>

                <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"

                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"

                    maxBufferPoolSize="524288" maxReceivedMessageSize="5000000"

                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"

                    allowCookies="false">

                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"

                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />

                    <reliableSession ordered="true" inactivityTimeout="00:10:00"

                        enabled="false" />

                    <security mode="Message">

                        <transport clientCredentialType="Windows" proxyCredentialType="None"

                            realm="" />

                        <message clientCredentialType="Windows" negotiateServiceCredential="true"

                            algorithmSuite="Default" establishSecurityContext="true" />

                    </security>

                </binding>

            </wsHttpBinding>

        </bindings>

        <client>

            <endpoint address="http://localhost:2429/Service1.svc" binding="wsHttpBinding"

                bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"

                name="WSHttpBinding_IService1">

                <identity>

                    <dns value="localhost" />

                </identity>

            </endpoint>

        </client>

    </system.serviceModel>

</configuration>

Now that our service has been added and configured correctly, let’s do some data binding to the data tables returned from our service. Open up the Data Sources window and drag and drop the Orders table onto Form1.

DragData

Finally, the last thing we need to do is add code to Form1’s OnLoad event to get data from our WCF service.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

 

namespace NorthwindTraders

{

    public partial class Form1 : Form

    {       

        private ServiceReference1.Service1Client serviceClient;

        public Form1()

        {

            InitializeComponent();

            this.serviceClient = new NorthwindTraders.ServiceReference1.Service1Client();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

          this.customersDataSet.Merge(this.serviceClient.GetOrders());

        }

 

        // Being a good citizen, always close a service connection

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            this.serviceClient.Close();

        }

    }

}

As you can see when the form is loaded and the dates changed, the validation occurs in a library separate from the client.

validation