Introduction
The point of using plain old class objects (POCO) is to abstract your data from the Entity Framework v4 (EF4) .edmx files for the purposes of portability, testing, data security, data validation, and providing data services; further abstraction is placing the POCO classes in a DLL separate from the EDMX file. This article does just that by demonstrating how to generate plain old class objects (POCO) using the .tt file plus .tt file (T4) generator and then pass these data context unaware POCOs through RIA Services SP1 Beta into Silverlight v4. For this example, you must have the following installed:
Also, you must have a database source that you will be connecting to; I am using SQL Server but you can use whatever.
Background
Microsoft does not make this easy - there is very little support or documentation for using POCO entities disconnected from the EDMX (i.e. in a separate DLL) with T4, EF4 and RIA services because the technology is so new. In fact, those poor people who implemented EF3.5 and an earlier version of RIA services will find that it has changed somewhat dramatically.
Connecting to Your Database
- Click File, New Project from inside of Visual Studio. You are going to select the project template ASP.NET Dynamic Data Entities Web Application. If you do not see this template, click Other Languages and select Visual C#. Use the information below:
- Project name: BasicDataViewer
- Type: ASP.NET Dynamic Data Entities Web Application
- Purpose: This project is where your EDMX file is stored and can display SQL Server, or Oracle or whatever EF4 generated entity in a website so that you can confirm your complex relationships.
- Right-click on your new project BasicDataViewer and select Add, New Item. You are going to add an ADO.NET Entity Data Model which can be found under the Visual C#, Data templates directory.
- Project name: BasicDataViewer
- Item name: DBData
- Type: ADO.NET Entity Data Model
- Purpose: This item will help you to create an entity model to logically map to your database (mapping is done automatically by EF4 when you save the .edmx).
- When you click OK, you will want to follow through the wizard in order to connect to your data source. Select 'Generate from database' and create a new connection. Check Save your connection settings to your Web.Config (it is a checkbox at the bottom of the Entity Data Model Wizard in the second step). In the third step of the wizard, you are going to choose which database tables to include in your model. When you click okay, it should create a basic outline of your model.
- In the .edmx editor, right-click on a blank space where there are no tables or associations being displayed and select Properties. You are going to change the following properties on your .edmx:
- Metadata Artifact Processing = Copy to Output Directory
- Namespace = DBDataModel
- Entity Container Name = DBData
- After rebuilding the solution, edit your web.config file and update the following setting (uncomment this line if is commented in the web.config).
DefaultModel.RegisterContext(typeof(DBData),
new ContextConfiguration() { ScaffoldAllTables = false });
- Press F5 to build and debug your project. It should display your data in a website with the relationships. If it does not or there are errors, you will need to work through them (old databases might complain about allowing
null
characters, in which case search for the field; you may need to edit the backend of your .edmx to set nullable="true"
).
Creating the POCO DLL using T4 (POCO Entity Generator)
- In the .edmx editor from
BasicDataViewer
project created previously, right-click on a blank space where there are no tables or associations being displayed and select Add Code Generated Item. You are going to create an ADO.NET POCO Entity Generator under Visual C# template types.
- Project name: BasicDataViewer
- Item name: DBDataModelPOCO
- Type: ADO.NET POCO Entity Generator
- Purpose: This item will generate T4 files which will create your POCO entities. It should add two items in the project called DBDataModelPOCO.Context.tt and DBDataModelPOCO.tt
- Create a new Class Library project in your solution (File, Add, New Project). Delete Class1.cs if it is created automatically.
- Project name: Entities
- Type: Class Library
- Purpose: This project will contain our POCO entities as well as validation, key attributes and perhaps basic business logic.
- Add reference to the framework assembly
System.Data.Entity
by right-clicking on the Entities project and selecting Add Reference.
- While holding down the <SHIFT> key click and drag the DBDataModelPOCO.Context.tt and DBDataModelPOCO.tt files from the
BasicDataViewer
project to the Entities project. You will notice that there are classes under the .tt files that are autogenerated.
- Double-click on each .tt file to edit them; you will be changing the location of the .edmx so that it knows how to build the classes:
string inputFile = @"..\BasicDataViewer\DBData.edmx";
- Next, right-click on each of your .tt files and select Run Custom Tool; this will recreate your POCO entities and confirm the connection to your .edmx.
- Build the Entities class library project by right-clicking on it and selecting the Build option.
- Add a reference to the
Entities
project in the BasicDataViewer
project by right-clicking on the BasicDataViewer
project and selecting Add Reference, Projects tab, Entities.
- Edit the
BasicDataViewer
web.config file and update the following setting:
DefaultModel.RegisterContext(typeof(Entities.DBData),
new ContextConfiguration() { ScaffoldAllTables = true});
- Finally, press F5 to build your project and test your POCO entities are being accessed correctly. If there are errors, you may need to work through them. I did not encounter any at this point.
Accessing the POCO Entities in Silverlight Using RIA Services
- Add a reference to
System.Componentmodel.DataAnnotation
in the Entities
project by right-clicking on the project and selecting Add Reference.
- The classes that need to be updated in step 3 are going to vary for you based on what tables you have setup in your .emdx file. It is easy to determine which classes to update by double-clicking on them under the .tt files and looking for primitive properties. Don't worry too much about getting this wrong, the compiler will complain that you have no keys for a particular class if you do not set this up correctly. Here is my structure, I only have one class called Zip.cs:
- DBDataModelPOCO.Context.tt
- DBDatamodelPOCO.Context.cs
- DBDataModelPOCO.tt
- DBDataModelPOCO.cs
- Zip.cs
- For all non-complex types, you will need to also define keys and metadata. In this example, I am only accessing one table called
Zip
. So, I edit the Zip.cs under DBDataModelPOCO.tt to include metadata and key attributes. This metadata is where you would have your validation and business logic. These metadata classes should be broken out into a separate file and referenced in the Zip class MetadataTypeAttribute
attribute, but I have placed them as internal classes so you can understand that they are 'buddies':
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
namespace Entities
{
[MetadataTypeAttribute(typeof(Entities.Zip.ZipMetadata))]
public partial class Zip
{
internal sealed class ZipMetadata
{
private ZipMetadata()
{
}
[Key]
public string ZipCode { get; set; }
}
#region Primitive Properties
public virtual string ZipCode
{
get;
set;
}
public virtual string City
{
get;
set;
}
public virtual string County
{
get;
set;
}
public virtual string State
{
get;
set;
}
public virtual decimal TaxRate
{
get;
set;
}
public virtual byte[] tstamp
{
get;
set;
}
#endregion
}
}
- Next, we are going to add a WCF RIA Services Class Library project to our solution. Do this by right-clicking on your solution and selecting add project. Then select WCF RIA Services Class Library from under the Silverlight templates.
- Project Name: EntRIAServices
- Type: WCF RIA Services Class Library
- Purpose: This provides RIA services to your Silverlight application. It will create two sub-projects, the one we are interested in has a .Web extension and should be called EntRIAServices.Web; the other sub-project is called EntRIAServices.
EntRIAServices
~ This is the main project
EntRIAServices
~ This is a sub-project
EntRIAServices.Web
~ This is a sub-project
- Delete the Class1.cs file that is automatically created in both sub-projects.
- Add reference to the framework assembly
System.Data.Entity
by right-clicking on the EntRIAServies.Web
sub-project and selecting Add Reference.
- Add reference to the
Entities
project by right-clicking on the EntRIAServies.Web
sub-project and selecting Add Reference.
- Rebuild the entire solution and check for errors. Any errors you encounter here should be related to step 3.
- Add a Domain Service Class to the
EntRIAServices.Web
sub-project called EntityDomainService
. Do this by right- clicking on the EntRIAServices.Web
sub-project and selecting Add, New Item, Domain Service Class. In the Add New Domain Service screen that appears, be sure to select <Empty Domain Service Class> under Available DataContext/ObjectContext classes and that Enable Client Access is checked.
- Project Name: EntRIAServices.Web
- Item Name: EntityDomainService
- Type: Domain Service Class
- Purpose: This is basically the WCF part of RIA services; it is the code generator for your service, I have no idea why Microsoft called it this. We are creating an empty Domain Service because we are not using Linq (we have our entities).
- Now that we have our RIA Services code generator, we are going to add access points to it. This can be done by creating an
iQueryable
, Invoke
or IEnumberable
interface, please see my notes below on how to get the correct connection string. Most of the errors I encountered here were related to referencing the metadata, which is a set of three files (csdl, msl, ssdl) created by our .edmx file (property Metadata Artifact Processing = Copy to Output) on save that serve to define your database's structure as it relates to your POCO entities; it would be nice to embed these as a resource.
namespace EntRIAServices.Web
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
[EnableClientAccess()]
public class EntityDomainService : DomainService
{
private Entities.DBData _context = new Entities.DBData
("metadata=C:\\Users\\Owner\\Documents\\Visual Studio 2010\\
Projects\\BasicDataViewer\\BasicDataViewer\\bin\\DBData.csdl|C:\\
Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\
BasicDataViewer\\BasicDataViewer\\bin\\DBData.ssdl|C:\\Users\\
Owner\\Documents\\Visual Studio 2010\\Projects\\BasicDataViewer\\
BasicDataViewer\\bin\\DBData.msl;provider=System.Data.SqlClient;
provider connection string='Data Source=SERVER\\DB_NAME;
Initial Catalog=YOUR_DATABASE_NAME;User ID=YOUR_DB_LOGIN;
Password=YOUR_DB_PWD;MultipleActiveResultSets=True'");
[Query]
public IQueryable GetZips()
{
return (IQueryable)_context.Zips.AsQueryable();
}
}
}
- Rebuild the entire solution, this will create a hidden folder under the
EntRIAServices
sub-project called Generated_Code
which is your RIA Services connection. You can ignore this or check it out to make sure it is there by showing all files on this sub-project. If it is not there on build, something went wrong and you will need to retrace your steps.
- Now we are going to add a SilverLight Business Application project to our solution to consume our RIA Services. You can do this by right-clicking on your solution and selecting Add, New Project, and choosing SilverLight Business Application under the Silverlight installed templates.
- Project Name: BusinessApplication1
- Type: Silverlight Business Application
- Purpose: Consume RIA Services and enable data edits via a user interface
- Step 12 will create two projects, one called
BusinessApplication1
and another project called BusinessApplication1.Web
. You are going to reference the EntRIAServices
project in the BusinessApplication1
project by right-clicking on BusinessApplication1
and selecting Add, Add Reference. Also, you will need to reference the EntRIAServices.Web
project in the BusinessApplication1.Web
project by right-clicking on BusinessApplication1.Web
and selecting Add, Reference.
- Rebuild the entire solution, twice. All errors should go away as code is generated to backup your RIA Service.
- Double-click on the MainPage.xaml of the
BusinessApplication1
project. This will bring up the Silverlight UI editor. Now, click Data, Show Data Sources. Okay, your entity connection should appear with all of the items. Click and drag it to the UI workspace. This should create a grid of the POCO entity object on the Silverlight workspace.
- Press F5 to build and run the solution. It should display a grid with your POCOs populated from the database. If it does not display or you get Not Found errors, make sure that you are not trying to return too many records (504 error) and that your RIA services Generated_Code hidden folder is in the
EntRIAServices
sub-project (500 error). Should this not be the problem and you cannot figure out the RIA error, try using Fiddler2
.
Creating a Domain Service Factory to Host POCO Entities
You will need to create a domain service factory to host your POCO entities correctly. A problem arises with using POCOs, which is that you must include relationships through Linq while hosting the data. Microsoft wrote an article on sharing entities using the Include statement, but it does not work with POCOs because they should be context unaware. Also, as is often the case, you might want to join two .edmx files or provide some highly customized business relationship between your entities or you might even want to do user access checks before providing data.
Please see the comments below and my associated article on Creating a Domain Service Factory to Host POCO Entities.
Conclusion
Here is a sample of this solution, you will need to use 7Zip to extract these files because of the high rate of compression:
Creating a POCO entity model from an .edmx entity model of a database model which is a model of your application data services in order to have RIA Services model it for your Silverlight application is killer overhead just to perform a little create, read, update and delete (CRUD) but I always felt the same about the MVVM structure because it makes changing the underlying database a chore.
If you want my opinion of RIA Services: it is too complex to setup for what it does and I found it error prone; however, at the same time once you have the six projects setup I can see it being very powerful in terms of being able to "quickly" model and host a logical database. It would be much easier if I could just check an attribute on my .edmx to use POCOs when I deploy the project, and the related business logic was somehow tied to the visible model. Anyhow, I wish I could help all of you out there beating on your keyboards trying to work through RIA Services and EF4, but that is not possible so I hope this article helps instead.
Sincerely,
Joseph Wykel, M.B.A., M.E., B.A.