Sunday, October 28, 2012

Entity Framework 5 Code First and WCF Ria Services

Starting from a question on stackoverflow site, I thought it could be useful to publish a working sample of Entity Framework 5 and WCF ria Services tight together.
Everything it’s pretty simple but there are some not obvious pitfalls that can lead to tedious waste of time.
First thing to do, create a new Silverlight Application and don’t forget to put a mark on “Enable WCF RIA Services” as in the image below
image
then, server side, on the project that will host our silverlight application (here named EFCodeFirst _WCFRiaDemo.Web) right click the project, then select “Manage Nuget Packages”; look for entity framework then click on install as in the image below
image
In my demo I’ll use the domain model classes from another assembly, as an usual path with POCO domain model, so let’s reference in the EFCodeFirst _WCFRiaDemo.Web project the assembly that contains our classes.
I’ll use a very basic one
image
Please note the BlogId field in the Post class. It really looks like the db that is leaking into the model, and indeed it is, but… that’s the way WCF Ria Services work with relations. Just pretend not to see this field Sorriso
Then create the DbContext including the related mapping, as in the snip below
public class BlogContext : DbContext
    {
        public BlogContext() : 
            base("BlogContext")
        {

        }
        public DbSet<Blog> Blogs { get; protected set; }

        public DbSet<Post> Posts { get; protected set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Here goes our custom mapping
            //we can comment out the row below and completely customize the entities mapping
            //base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Post>()
                .HasRequired(x => x.Blog)
                .WithMany(x => x.Posts)
                .HasForeignKey(x => x.BlogId);


        }
    }


and in the web.config add the connection strings section and relevant info

<connectionStrings>
    <add name="BlogContext" connectionString="Data Source=.\SQLEXPRESS; AttachDBFilename=|DataDirectory|\WcfRiaDemo.mdf;Integrated Security=SSPI; MultipleActiveResultSets=True;User Instance=true" providerName="System.Data.SqlClient" />
</connectionStrings>


I’ve leveraged Package Manager Console to create my database running in it Enable-Migrations –EnableAutomaticMigrations

Now, our DbContext should be up and running, let’s add out DomainService class. I’m not a fan of shining wizard, so I’ll not leverage automatic DomainServices creation, taking into account that it needs some tricks in order to work with EF Code First. I’ll rather add a DbDomainService manually. Consider also, that this is the way you’ll normally deal with when adding classes and methods to your model.

In order to work with EntityFramework and take advantages of mapping info defined into the DbContext, we need to derive our DomainContext from the DbDomainService class. This is a class defined in the WCF Ria Services tookit, available here.

Then, “expose” our classes to the client, adding methods these

    [EnableClientAccess()]
    public class WCFRiaDemoDomainService : DbDomainService<BlogContext>
    {
        [Query]
        public IQueryable<Blog> GetBlogs()
        {
            return this.DbContext.Blogs;
        }

        [Query]
        public IQueryable<Post> GetPosts()
        {
            return this.DbContext.Posts;
        }
    }

The QueryAttribute  it’s not strictly needed as it will be picked from convention (the method returns an IQueryable<T>) however, I like to mark those methods.

Please note that I’m not exposing the related Insert/Delete/Update methods, so my entity will be generated as read-only.


Now come the tricky part: if we try to build the client it will NOT generate our DomainContext, but rather just the WebContext and no Classes.

This come from a version compatibility issue between the code generator and entitiy framework. The Code generator will look for EntityFramework v4.2, will of course not found it, and will not generate our classes and unfortunately will not notify us of such problem.

The solution I usually use rely on, is assembly binding redirect. Putting the lines below into our web.config will trick the code generator that finally will generate our proxy

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="EntityFramework" publicKeyToken="b77a5c561934e089" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>



A final note: Consider that the proxy generator come in two flavours: the CodeDom generator (the one that we are using here) and the T4 based code generator. You can learn how to use and leverage it from the (no long updated) blog of varun puranik

That’s it for now. I’ve attached the solution for everyone to download and take a look.

15 comments:

Anonymous said...

thanks for your time and sample

Anonymous said...

a bit too detailed maybe, but thanks for your effort

Anonymous said...

Hi, thanks for the post. Would you be able to add a simple example of how to consume the Iqueryable in the xaml page. As well as say display the Blogs in a datagrid. I am having no luck in finding any help on the web.

Anonymous said...

Hi, I tried your example but I get the error:
Error 1 Failed to get the MetadataWorkspace for the DbContext type 'EFCodeFirst_WCFRiaDemo.Web.BlogContext'. EFCodeFirst_WCFRiaDemo.Silverlight


Any thoughts?
Using VS 2012.

Dave said...

Good article - however the sample doesn't seem to initially compile on VS2012.2

Error 1 Failed to get the MetadataWorkspace for the DbContext type 'EFCodeFirst_WCFRiaDemo.Web.BlogContext'. EFCodeFirst_WCFRiaDemo.Silverlight


Have not dug in deep yet.. will post back if I find answers.

Dave said...

Ahh - run VS as administrator...

youNme said...

Hi,getting the same error (Failed to get the MetadataWorkspace for the DbContext type..) after adding those mentioned lines to web.config.I ran VS as administrator. Any thoughts how to fix this?

Thanks

Marco Casamento said...

@youNme is your cnnString valid and is your database reachable when you build your solution ? The MetadataDescriptor for EntityFramework has a nasty dependency on the database.

If it's reachable, then I suggest to investigate further on the condition, attaching Visual Studio to the msbuild process you'll be able to see insight the exception. Here is how: In Visual studio on the properties page of your web project, go to Web, the select "Start external program" and put the full path to your msbuild.exe (tipically is C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe, be careful to select the 32bit version) and in the command line arguments, put the full path to the silverlight project (.csproj) that contains your proxy (the silverlight one with the reference to the server side project). Under Debug/Exceptions be sure that your debugger will break when it encounter ANY Common Language Runtime Exception, then start your web by pressing F5. You'll see a cmd box that outputs the result of your compilation, and your debugger will stop in many (often insignificants) point. Take note of the last chain of errors and report it here, I'll try to help you then.

sheuly said...

Hi,
Thanks you so much for your quick reply.
I have checked the cnnString which should be ok,but dont know how to test if the database reachable when build the solution.
Then, I also tried I tried to attach the external program-MSBuild.exe, seems blow up with "{MSBUILD : error MSB1008: Only one project can be specified.\r\nSwitch: studio}"

Would it be possible that I attach you a sample app?

Marco Casamento said...

ok, np, upload it somehwere

sheuly said...

Hi, please find the sample app from here: https://dl.dropboxusercontent.com/u/59312943/RiaServicesCodeFirstExample.zip

Thanks


Marco Casamento said...

@sheuly, the project you sent lacks the database, your connection string references to SHEULYS11D\DEVSQL instance. I suggest you to use a local database (AppData) and resend the sample

Marco Casamento said...
This comment has been removed by the author.
sheuly said...


Hi,I have updated the sample with local db.
https://dl.dropboxusercontent.com/u/59312943/RiaServicesCodeFirstExample.zip

Just to inform, the Nuget package "EntityFramework" does not contain the required assembly for DbDomainService.
So I added the package "RIAServices.EntityFramework". After using this one and removing "EntityFramework" package I do not get any
error if I add the binding redirect(as you have mentioned) to the web.config. But then the problem is, it does not create any proxy code for the service.

Thanks

Marco Casamento said...

It wasn't a problem with RIA, but rather with your DBContext. Have a look at entity framework documentation. Google It.
Here is the updated version, http://www.ge.tt/5S7Zebs/v/0?c your domain model classes must be public and not nested.