As James has already posted here there are concerns circulating around the web about the lack of any coherent guidance in terms of a good approach to using LINQ to SQL (or Entities for that matter) in an n-tier architecture. One of the issues I've been fretting over is what I refer to as the "types and references" issue, namely that with the approaches described in the MSDN articles covering the topic there's no real explanation as to how the entity types generated by the IDE from the dbml file can be consumed as deserialised entities on the client side without creating some kind of code "disconnect" between LINQ entities which are generated automatically from the DB schema and a different set of definitions which can be used by the client.
There are various possible solutions to this, many of which look promising at the start but ultimately end up being dead ends or not really practical. One approach that I have been able to get to work is outlined below. What we're going to do in this first part is simply create aWCF Service component which will return a list of Customers from the Northwind database and consume that component in a simple client.
The Data Component
1. To start, we fire up Visual Studio 2008 and create an empty Solution. To that we want to add a Class Library project which we'll call NorthwindData.
2. Next, we add a dbml file to the project by going to "Add New" and selecting the "LINQ to SQL Classes" entry and call it Northwind.dbml. Then use Server Explorer to navigate or connect to your SQL Server and drag the Customers table onto the dbml designer surface from the Northwind database.
3. We now want to add a partial class so that we can add our own code to the generated DataContext. So add a new Class file and add the following code which will provide a method which returns all customers.
The WCF Service
4. So far so good. But now it's time to create our WCF Service component. So add a New Project to the solution and select WCF Service Library, we'll call it NorthwindService. We now need to make a few changes to the service framework that VS has created for us. First we need to add references to the NorthwindData project and to System.Data.Linq. Then we need to change the default Interface and Service names etc so that they are a bit more meaningful for our purposes. To do this we first rename the IService1.cs file to INorthwindService.cs (when you are asked to rename all references say Yes otherwise you will have to change the name of the actual interface yourself too.)
In WCF, Service interfaces are defined as "Service Contracts" and are marked with the ServiceContractAttribute, the methods on those interfaces are marked with the OperationContractAttribute. What this basically does is specify that the interfaces and marked operations define a contract that is upheld by any service which advertises itself as implementing that interface. If you've done much work with web services then you'll be quite at home with this concept as what it is effectively doing is decorating the interface so that our service can advertise and describe itself in a way synonymous with the WSDL approach. At the moment we're only interested in returning one list of Customers so our contract is relatively simple. The interface should be defined as follows:
5. The next simple step is to provide an implementation of the interface in our component, so we rename Service1.cs to NorthwindService.cs (again allow VS to rename all references etc) and change the implementation code to the following:
7. At this point, we only have one more tiny problem with our service. In point 4 we learned that interfaces exposed by WCF services specify a "contract" and this is also extended to any data objects which are passed through that interface, this is done using the DataContractAttribute (and the associated other attributes which define the members on the entity class.) So how do we get the entity objects generated by the IDE to be decorated with the required interfaces. This took a considerable amount of digging but in the end turned out to be a remarkably simple task. If we go back to our NorthwindData project and go to the properties of the dbml in the designer we can see a property called Serialization Mode. All we have to do is change the setting to "Unidirectional".
What this then does is automatically marks any of the entities generated from the dbml with the DataContract attributes.
It also means that the data definition of the entities can be included in the service definition of the interface contract. In a practical sense what this does is allow us to add a reference to the service and Visual Studio 2008 (utilising svcutil.exe under the hood) will read the metadata for the service and generate a client-side service proxy containing definitions of the LINQ entities for us.
8. Let's test our service. Right-click on the NorthwindService project and select "Debug->Start New Instance..." This should force a compile and run the WCF Service which will be hosted by default in the "WCF Service Host" application which will place an icon in the notification area. If the service has been configured correctly the WCF Test Client should launch and after a second or so your service should be loaded into the tree view on the left. You can then expand the tree nodes to see the structure of the Service Interface and double click on the "GetCustomerList()" node. This will open a tab on the right which has an "Invoke" button which, when clicked, will call the method on the service and display the results in the lower box. As you can see below, the call returned 91 customer entries.
The Simple Client
8. To create our simple client we'll add a console application project to our solution. This will serve our purposes in Part 1 as we're just interested in getting a list of customers back from the database but in future parts I hope to create a more complex and useable client using WPF. Add a console project called SimpleClient then right click on the References node in Solution Explorer and select "Add Service Reference..." If everything has gone right, you should be able to click the "Discover" button and the service details should appear in the tree on the left. When you expand the node, the service is instantiated and you can navigate to the interface definition. Before we commit the reference, change the Namespace entry at the bottom to Northwind. Finally, by default collections appear in the proxy as arrays, to make them a List
9 Click OK in the dialog, and the service reference will be added to the project. We can then add the following code to the Main method in Program.cs:
To test the simple application, ensure an instance of the service is running in the WCF Test Client (if one isn't, then right click the service project and select "Debug->Start New Instance".) then start an instance of the console application. All things being well, you should have a console window with a list of Northwind customers displayed.
So this is where we'll leave it for now. What we've achieved is fairly simplistic but what we have is an overall framework upon which we can build more complex behaviours such as updates and creation.