共用方式為


Using WCF Data Services with Entity Framework 4.1 and Code First


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


In this post you’ll see how Code First and the DbContext class in Entity Framework 4.1 RC will work with WCF Data Services going forward. WCF Data Services in .NET 4.0 was released prior to the Entity Framework 4.1 RC and so does not natively know about the DbContext class. There are numerous posts already on how to get DbContext to work with WCF Data Services in .NET 4.0, including this one by Rowan Miller. These posts talk about how to write some extra code to initialize the DataService class. This is not how we envisioned these technologies working together. However, it is possible to see how these technologies were meant to work together by using EF 4.1 RC along with the Microsoft WCF Data Services 2011 CTP2. In this walk through we’ll build a simple WCF Data Service using Code First with the latest and greatest releases. This post does assume some knowledge of Code First, DbContext, and WCF Data Services.

Getting Started

1. Download and install Microsoft WCF Data Services 2011 CTP2.

2. Open Visual Studio 2010 and create an ASP.NET Empty Web Application called “HospitalWeb”

3. Using the NuGet package manager, add the Entity Framework 4.1 RC to your project by installing the “EntityFramework” package.

Alternatively, you can download and install the full Entity Framework 4.1 RC setup. If you do this, you should add a reference to “EntityFramework.dll” in your project.

Create your data model

We’ll be using the Code First approach to build our data model and so will be using the DbContext API to help surface that. Here is what you need to do to your project:

1. Add a new code file to your project called “Model.cs”.

2. Add the following entity classes to your file:

public class Patient

{

    public int Id { get; set; }

       

    [MaxLength(64)]

    public string Name { get; set; }

    public virtual ICollection<LabResult> LabResults { get; set; }

}

public class LabResult

{

    public int Id { get; set; }

    public string Result { get; set; }

}

 

3. Add a DbContext class to your project. In this case, I’ve called mine HospitalContext.

public class HospitalContext : DbContext

{

    public DbSet<Patient> Patients { get; set; }

    public DbSet<LabResult> LabResults { get; set; }

}

 

 

4. You can also optionally add a Database initializer to your HospitalContext. One of the nice things about writing new applications with DbContext is that it can optionally create a database for you based on your .NET classes or entity data model, and also optionally pre-populate that database with seed data. Here is the initializer I am using:

public class HostpitalContextInitializer :

                 DropCreateDatabaseIfModelChanges<HospitalContext>

{

  protected override void Seed(HospitalContext context)

    {

        context.Patients.Add(new Patient { Name = "Fred Peters" } );

        context.Patients.Add(new Patient { Name = "John Smith" } );

        context.Patients.Add(new Patient { Name = "Karen Fredricks" } );

    }

}

To have your HospitalContext use this initializer, I added a static constructor to my HospitalContext with the method call to start using the initializer:

static HospitalContext()

{

    Database.SetInitializer(new HostpitalContextInitializer());

}

Your data model and context are now ready to be used with WCF Data Services. The first time a HospitalContext is created by our service, a database will be created for us and our seed data will be inserted.

Create your data service

The Microsoft WCF Data Services 2011 CTP2 did not include updated Visual Studio item templates, so I’ll show you how to use the existing item templates and fix them up to use the CTP:

1. Add a new project item to your HospitalWeb project, choosing “Web” and then “WCF Data Service” in the Visual Studio Add Project Item dialog. I called my service “HospitalService.svc”.

2. The WCF Data Service that was added to your project refers to .NET 4.0 only, not to CTP2, so we’ll need to make a few changes:

a. Remove the project references to “System.Data.Services” and “System.Data.Services.Client” from your project.

b. Add project to references “Microsoft.Data.Services” and “Microsoft.Data.Services.Client” from your project. Note that these assemblies are not installed in the GAC as part of WCF Data Services 2011 CTP2 installation, so you’ll need to browse to the installation directory to find them.

c. In your “HosptialService.svc.cs” file, change the service version from V2 to V3:

config.DataServiceBehavior.MaxProtocolVersion =

DataServiceProtocolVersion.V3;

Use your data model with your data service

Now you are ready to use your HospitalContext with your WCF Data Service. Here is all you need to do:

 

1. In your “HospitalService.svc.cs” file, change your HospitalService class to be a DataService<HospitalContext>:

public class HospitalService : DataService<HospitalContext>

2. Change the access permissions to the two sets of data we are interested in, Patients and LabResults:

public static void InitializeService(DataServiceConfiguration config)

{

    config.SetEntitySetAccessRule("Patients", EntitySetRights.AllRead);

    config.SetEntitySetAccessRule("LabResults", EntitySetRights.AllRead);

    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;

}

3. You can also optionally add a service operation to your service to run custom queries and business logic using your HospitalContext. Below is a service operation method to retrieve all patients by name:

[WebGet]

public IQueryable<Patient> GetPatientsByName(string name)

{

    return CurrentDataSource.Patients.Where(p => p.Name.Contains(name));

}

 

Notice that CurrentDataSource is strongly typed to your HospitalContext, which is a DbContext instance from Entity Framework 4.1.

 

4. Finally, give permissions to your service operation in the InitializeService method:

config.SetServiceOperationAccessRule("GetPatientsByName",

                                         ServiceOperationRights.AllRead);

 

Run your service

You are now ready to run things. Go ahead and press F5. A bunch of things are going to happen:

1. Code First is going to build your data model using the Patient and LabResult classes.

2. The DbContext class is going to use that data model to create a database for you and populate it with our seed data.

3. A WCF Data Service is going to start up and display the metadata for the service

You can now try out a few WCF Data Services queries. Here are a few I played with:

1. Return all patients:

https://localhost:55051/HospitalService.svc/Patients/

2. Call the service operation GetPatientsByName and search for “Fred”:

https://localhost:55051/HospitalService.svc/GetPatientsByName?name='Fred'

 

As you can see, we’ve tried to make using the DbContext class and Code First seamless with DataServices. Both of the releases used in this post are in pre-release form, so if you find any issues with using them, using them together, or with the experience, please let us know!

Jeff Derstadt

Data Frameworks Team

Comments

  • Anonymous
    March 21, 2011
    Hello Jeff ! Can you explain please,  when we use Data Services   we have only     open unsecure http  chanell for  transfer data ( basic http binding ) ? I want to use something like net.tcp,  oh wshttpbinding with strong encription. It is possible ?

  • Anonymous
    March 21, 2011
    Hi Jeff! Great post! I have a question though. I have a somewhat different set-up than what you based your example on. I have a search-engine which I'd like to publish through a WCF-data service and thought that I, by doing the following, would have a solution:

  1. Create the 'model' by defining classes representing the objects residing in the search-index
  2. Create the DbContext publishing (throught DbSet:s) the objects through properties on my DbContext-derived class
  3. Create a data-service and define a service operation querying the search-engine When trying this I get an exception from the data-service stating that I don't have "create database in ...". I understand that in a majority of situations we're dealing with relational-data here but not in this case. I'd be very happy if you could point me in the right direction here... Again, thanks for a great post! Cheers, /Peter
  • Anonymous
    March 22, 2011
    @Peter If you are not targetting a relational database as your backend store, then using the Entity Framework with WCF Data Services is not the way to go as the Entity Framework targets primarily relational databases. Both EF and Data Services have provider models. In EF, the provider model is that anything with an ADO.NET provider should work, at least for some things. There are several existing providers for common backends such as SQL Server, Oracle, DB2, mySQL, etc, or you can write your own. Data Services also has an interesting provider model and if your goal is to use Data Services over something no-relational, you may want to explore that. I'd start here: msdn.microsoft.com/.../dd672591.aspx

  • Anonymous
    March 22, 2011
    I'll check out the link you provided! Thanks for your help Jeff! Cheers, /Peter

  • Anonymous
    March 23, 2011
    After following along with the instructions given here, I'm encountering an error that I don't understand. This comes up as soon as I run the service. Request Error The server encountered an error processing the request. The exception message is 'Model compatibility cannot be checked because the database does not contain model metadata. Ensure that IncludeMetadataConvention has been added to the DbModelBuilder conventions.'. The only thing I did differently from the instructions above is to use my own connection string instead of the default (which would use SQLEXPRESS). I named the connection string the same as the context name, which I saw I would need to do from another blog. This is how it appears in the web.config: <add name="HospitalContext" connectionString="Data Source=LT1DEVSQL;Initial Catalog=DEV_TEST;User ID=;Password=;" providerName="System.Data.SqlClient" /> This points to an empty database with no tables, etc. I was expecting the tables corresponding to Patients and LabResults to be created in this empty database. Anyhow, if you have any recommendations I'd really appreciate it. Thanks, Matt

  • Anonymous
    March 29, 2011
    How about somebody doing a model first, winform, datagridview, master/detail, observable example. I have spent considerable time trying to piece this together from all these blogs and I have not been successful. Seems to me this would be fairly common scenrio.

  • Anonymous
    April 01, 2011
    It would be nice to see a wcf ria example...Is this possible??  

  • Anonymous
    May 05, 2011
    Matt,   From what I understand in order to take advantage of the database creation, you need to use SQLExpress in order to generate the initial DB.... Im not quite sure as to the reasoning behind it, but whenever I try to use the DB initialization side, on a -non-sqlexpress instance I get the same error.

  • Anonymous
    May 24, 2011
    I tried hosting a wcf data service using the local iis web server. whenever i try viewing the service in browser i receive the following error: Request Error - The server encountered an error processing the request. See server logs for more details. I, however, am able to access the service using the default Visual Studio Development Server

  • Anonymous
    May 30, 2011
    Does the DataService now work against an interface? Or literally types derived from ObjectContext and DataContext?

  • Anonymous
    July 13, 2011
    the error could be access rights on the sql database.

  • Anonymous
    August 25, 2011
    i am getting the followi 'System.Data.Objects.ObjectContext' does not contain a definition for 'Address' and no extension method 'Address' accepting a first argument of type 'System.Data.Objects.ObjectContext' could be found (are you missing a using directive or an assembly reference?) below  is my code. using System; using System.Collections.Generic; using System.Data.Services; using System.Data.Services.Common; using System.Linq; using System.ServiceModel.Web; using System.Web; using System.Data.Services.Client; using System.Data.Objects; using System.Data.Entity.Infrastructure; using CodeFirstMVC.Models; namespace eGate.BackOffice.WebClient.WCF_Services {    public class WcfDataService1 : DataService<ObjectContext>    {        // This method is called only once to initialize service-wide policies.        public static void InitializeService(DataServiceConfiguration config)        {            config.SetEntitySetAccessRule("*", EntitySetRights.All);            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;        }        protected override ObjectContext CreateDataSource()        {            AddressContext addrContext = new AddressContext();            // Get the underlying ObjectContext for the DbContext.            var context = ((IObjectContextAdapter)addrContext).ObjectContext;            context.ContextOptions.ProxyCreationEnabled = false;            // Return the underlying context.            return context;        }        [WebGet]        public IQueryable<Address> GetAllAddress()        {            return CurrentDataSource.Address;         }    } }

  • Anonymous
    October 04, 2011
    Hi Jeff, Do you know if this is possible with the June 2011 CTP and EF 4.1? I'm getting the error below: The server encountered an error processing the request. The exception message is 'Expression of type 'System.Data.Objects.ObjectContext' cannot be used for return type 'System.Data.Objects.ObjectContext''. See server logs for more details. The exception stack trace is: at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection1 parameters) at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable1 parameters) at System.Data.Services.Providers.DbContextHelper.CreateDbContextAccessor(Type type) at System.Data.Services.Providers.DbContextHelper.GetDbContextAccessor(Type type) at System.Data.Services.DataService1.CreateProvider() at System.Data.Services.DataService1.HandleRequest() at System.Data.Services.DataService`1.ProcessRequestForMessage(Stream messageBody) at SyncInvokeProcessRequestForMessage(Object , Object[] , Object[] ) at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc) at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

  • Anonymous
    December 20, 2011
    Hi Jeff, I discovered a serious bug. I searched a whole day for  the reason why my model didn't word like your example. I always got an exception in function 'System.Data.Common.DbConnectionOptions.GetKeyValuePair(String connectionString, Int32 currentPosition, StringBuilder buffer, Boolean useOdbcRules, String& keyname, String& keyvalue)' stating that the format is not correct. After playing around I discovered that the problem was a database name with spaces in the name. Then the connection string has additional spaces which are not correctly handled by the DbConnectionOptions parser. After using a databasename without spaces my example worked like charm.