WCF Web API + EF Code First

Topics: Web Api
Sep 22, 2011 at 3:34 AM

Somewhere I though I read that you could return EF Code First objects directly from WCF Web API services. I have tried this and get a serialization error.

What I really want is to use the functionality where I return an IQueryable so then the query gets passed through WCF -> EF -> SQL (which is a pretty cool notion)

Here is what I have

    [ServiceContract]
    public class TestApi
    {
        [WebGet]
        public IQueryable<Parent> Get()
        {
            // just return array instead of getting data from EF DbContext
            return new[] { 
                new Parent { Name = "Test1" },
                new Parent { Name = "Alkgsdjisdu" }, }.AsQueryable();
        }
    }

    public class Parent
    {
        public string Name { get; set; }
        public int Number { get; set; }
        public virtual ICollection<Child> Children { get; set; }
    }

    public class Child
    {
        public string Name { get; set; }
    }

But unfortunately I get a serialization error when accessing the service:

Cannot serialize member WebApplicationTest.Apis.Test.Children of type System.Collections.Generic.ICollection`1[[WebApplicationTest.Apis.Child, WebApplicationTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.

Is this actually possible? If so how can I do this?

Thanks

Sep 22, 2011 at 5:16 AM

Try and use Collection instead of ICollection – and also remember to add UriTemplate on even it is empty (we’ll fix that in the future):

[ServiceContract]

public class TestApi

{

[WebGet(UriTemplate = "")]

public IQueryable<Parent> Get()

{

// just return array instead of getting data from EF DbContext

return new[] {

new Parent { Name = "Test1" },

new Parent { Name = "Alkgsdjisdu" }, }.AsQueryable();

}

}

public class Parent

{

public string Name { get; set; }

public int Number { get; set; }

public virtual Collection<Child> Children { get; set; }

}

public class Child

{

public string Name { get; set; }

}

That allows you to do query composition – for example you can issue a request with URI

…?$top=1

Henrik

From: gregbacchus [email removed]
Sent: Wednesday, September 21, 2011 7:35 PM
To: Henrik Frystyk Nielsen
Subject: WCF Web API + EF Code First [wcf:273411]

From: gregbacchus

Somewhere I though I read that you could return EF Code First objects directly from WCF Web API services. I have tried this and get a serialization error.

What I really want is to use the functionality where I return an IQueryable so then the query gets passed through WCF -> EF -> SQL (which is a pretty cool notion)

Here is what I have

    [ServiceContract]
    public class TestApi
    {
        [WebGet]
        public IQueryable<Parent> Get()
        {
            // just return array instead of getting data from EF DbContext
            return new[] { 
                new Parent { Name = "Test1" },
                new Parent { Name = "Alkgsdjisdu" }, }.AsQueryable();
        }
    }
 
    public class Parent
    {
        public string Name { get; set; }
        public int Number { get; set; }
        public virtual ICollection<Child> Children { get; set; }
    }
 
    public class Child
    {
        public string Name { get; set; }
    }

But unfortunately I get a serialization error when accessing the service:

Cannot serialize member WebApplicationTest.Apis.Test.Children of type System.Collections.Generic.ICollection`1[[WebApplicationTest.Apis.Child, WebApplicationTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.

Is this actually possible? If so how can I do this?

Thanks

Coordinator
Sep 22, 2011 at 5:39 AM

Not using UriTemplate is fine if you are using our MapServiceRoute method / WebApiConfiguration in Enhancements. It applies the blank template by default.

Coordinator
Sep 22, 2011 at 5:46 AM
Edited Sep 22, 2011 at 5:47 AM

I think the issue here if you are using EF code first is that it swaps the collection implementations at runtime, thus making it concrete won't work as EF will not be able to override the collections. The serializer does not know how to deal with interfaces, so it blows. The easiest way to handle this is to create DTOs that don't return ICollection and have an extension method which does the conversion.

Sep 22, 2011 at 6:35 AM

Thanks for the reply. This almost works... that is the two parts work separately, although unfortunately not when put together. What I mean by that is:

1) the modified code you have listed works nicely;

2) EF code-first still works fine with replacing ICollection<> with Collection<>;

3) Unfortunately if I put them together:

        [WebGet]
        public IQueryable<Parent> Get()
        {
            using( var db = new DataContext() )
            {
                var results = db.Parents
                    .Include( "Children" );
                return results;
            }
        }

VS thinks it works (I can see that it loads parent and children, there are no errors), but the browser (Chrome) gets

Error 324 (net::ERR_EMPTY_RESPONSE): The server closed the connection without sending any data.

And Charles Debugging Proxy shows: Status: Failed; Failure: Remote server closed the connection before sending response header

Any further thoughts?

Thanks

 

Coordinator
Sep 22, 2011 at 6:49 AM

Have you tried putting break on all errors? You can either use the source, or we have symbol packages at http://www.symbolsource.org/Public/Metadata/NuGet. It's possible the serializer is breaking when it tries to actually serialize the collection. We've seen issues before related to trying to serialize EF types.

Sep 22, 2011 at 9:15 AM

Thanks. Will try.

Sep 22, 2011 at 10:04 AM

Thank you, that helped solved it. It was my using() statement. I was getting an ObjectDisposedException... makes sense really. Will it be safe (resource wise) not to dispose the DbContext? Or should I make it static? How is it intended to be used?

I guess making it a member field would solve the problem too, right?

Thanks for the help.

Sep 22, 2011 at 10:35 AM

Oops. Spoke too soon. Worked fine with no data being returned. Add some records into the table and I get

System.InvalidOperationException was unhandled by user code
  Message=There was an error generating the XML document.
  Source=System.Xml
  StackTrace:
       at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
       at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o)
       at System.Net.Http.Formatting.XmlMediaTypeFormatter.OnWriteToStream(Type type, Object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\XmlMediaTypeFormatter.cs:line 305
       at System.Net.Http.Formatting.MediaTypeFormatter.WriteToStream(Type type, Object instance, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\MediaTypeFormatter.cs:line 335
       at System.Net.Http.ObjectContent.WriteToStreamInternal(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 755
       at System.Net.Http.ObjectContent.SerializeToStream(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 365
       at System.Net.Http.HttpContent.CopyTo(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 198
       at System.Net.Http.HttpContent.CopyTo(Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 217
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 256
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 212
       at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message)
       at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
       at System.ServiceModel.Channels.HttpRequestContext.OnReply(Message message, TimeSpan timeout)
       at System.ServiceModel.Activation.HostedHttpContext.OnReply(Message message, TimeSpan timeout)
       at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout)
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncodingRequestContext.Reply(Message message, TimeSpan timeout) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncodingRequestContext.cs:line 99
       at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc)
  InnerException: System.InvalidOperationException
       Message=The type System.Data.Entity.DynamicProxies.Parent_8F972B3EEED726B14DD4589EF146B9CA4312CDAC9E8E7B4FE8C3FB1436DA1806 was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
       Source=3nsisc24
       StackTrace:
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write3_Parent(String n, String ns, Parent o, Boolean isNullable, Boolean needType)
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write4_ArrayOfParent(Object o)
       InnerException: 

Here is my complete code (excluding Global.asax.cs):

using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace WebApplication1
{
    [ServiceContract]
    public class TestApi
    {
        [WebGet( UriTemplate = "" )]
        public IQueryable<Parent> Get()
        {
            var db = new DataContext();
            var jobs = db.Parents
                .Include( "Children" );
            return jobs;
        }
    }

    public class DataContext : DbContext
    {
        public DbSet<Parent> Parents { get; set; }
    }

    public class Parent
    {
        [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )]
        public int Id { get; set; }
        public string Name { get; set; }
        public int Number { get; set; }
        public virtual Collection<Child> Children { get; set; }
    }

    public class Child
    {
        [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )]
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

Coordinator
Sep 22, 2011 at 10:52 AM
If you read the error, it is the proxy which is the problem. EF emits a proxy type at runtime. Our serializer doesn't know how to handle it. The error says use KnownTypes but you cannot because it is dynamic at runtime.

DTOs are your friend here


From: gregbacchus
Sent: 9/22/2011 2:35 AM
To: Glenn Block
Subject: Re: WCF Web API + EF Code First [wcf:273411]

From: gregbacchus

Oops. Spoke too soon. Worked fine with no data being returned. Add some records into the table and I get

System.InvalidOperationException was unhandled by user code
  Message=There was an error generating the XML document.
  Source=System.Xml
  StackTrace:
       at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
       at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o)
       at System.Net.Http.Formatting.XmlMediaTypeFormatter.OnWriteToStream(Type type, Object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\XmlMediaTypeFormatter.cs:line 305
       at System.Net.Http.Formatting.MediaTypeFormatter.WriteToStream(Type type, Object instance, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\MediaTypeFormatter.cs:line 335
       at System.Net.Http.ObjectContent.WriteToStreamInternal(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 755
       at System.Net.Http.ObjectContent.SerializeToStream(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 365
       at System.Net.Http.HttpContent.CopyTo(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 198
       at System.Net.Http.HttpContent.CopyTo(Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 217
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 256
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 212
       at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message)
       at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
       at System.ServiceModel.Channels.HttpRequestContext.OnReply(Message message, TimeSpan timeout)
       at System.ServiceModel.Activation.HostedHttpContext.OnReply(Message message, TimeSpan timeout)
       at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout)
       at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncodingRequestContext.Reply(Message message, TimeSpan timeout) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncodingRequestContext.cs:line 99
       at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc)
  InnerException: System.InvalidOperationException
       Message=The type System.Data.Entity.DynamicProxies.Parent_8F972B3EEED726B14DD4589EF146B9CA4312CDAC9E8E7B4FE8C3FB1436DA1806 was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
       Source=3nsisc24
       StackTrace:
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write3_Parent(String n, String ns, Parent o, Boolean isNullable, Boolean needType)
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write4_ArrayOfParent(Object o)
       InnerException: 

Here is my complete code (excluding Global.asax.cs):

using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace WebApplication1
{
    [ServiceContract]
    public class TestApi
    {
        [WebGet( UriTemplate = "" )]
        public IQueryable<Parent> Get()
        {
            var db = new DataContext();
            var jobs = db.Parents
                .Include( "Children" );
            return jobs;
        }
    }

    public class DataContext : DbContext
    {
        public DbSet<Parent> Parents { get; set; }
    }

    public class Parent
    {
        [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )]
        public int Id { get; set; }
        public string Name { get; set; }
        public int Number { get; set; }
        public virtual Collection<Child> Children { get; set; }
    }

    public class Child
    {
        [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )]
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

Sep 22, 2011 at 12:10 PM

Switch over to json serialization and it will work like a charm.

On Sep 22, 2011 6:52 AM, "gblock" <notifications@codeplex.com> wrote:
> From: gblock
>
> If you read the error, it is the proxy which is the problem. EF emits a proxy type at runtime. Our serializer doesn't know how to handle it. The error says use KnownTypes but you cannot because it is dynamic at runtime.
>
> DTOs are your friend here
>
> From:gregbacchus
> Sent:9/22/2011 2:35 AM
> To:Glenn Block
> Subject:Re: WCF Web API + EF Code First [wcf:273411]
>
> From: gregbacchusOops. Spoke too soon. Worked fine with no data being returned. Add some records into the table and I getSystem.InvalidOperationException was unhandled by user code Message=There was an error generating the XML document. Source=System.Xml StackTrace: at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id) at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o) at System.Net.Http.Formatting.XmlMediaTypeFormatter.OnWriteToStream(Type type, Object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\XmlMediaTypeFormatter.cs:line 305 at System.Net.Http.Formatting.MediaTypeFormatter.WriteToStream(Type type, Object instance, Stream stream, HttpContentHeaders contentHeaders, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\Formatting\MediaTypeFormatter.cs:line 335 at System.Net.Http.ObjectContent.WriteToStreamInternal(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 755 at System.Net.Http.ObjectContent.SerializeToStream(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http.Formatting\System\Net\Http\ObjectContent.cs:line 365 at System.Net.Http.HttpContent.CopyTo(Stream stream, TransportContext context) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 198 at System.Net.Http.HttpContent.CopyTo(Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.Net.Http\System\Net\Http\HttpContent.cs:line 217 at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Stream stream) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 256 at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncoderFactory.HttpMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncoderFactory.cs:line 212 at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message) at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout) at System.ServiceModel.Channels.HttpRequestContext.OnReply(Message message, TimeSpan timeout) at System.ServiceModel.Activation.HostedHttpContext.OnReply(Message message, TimeSpan timeout) at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout) at Microsoft.ApplicationServer.Http.Channels.HttpMessageEncodingRequestContext.Reply(Message message, TimeSpan timeout) in S:\Samples\wcf_1e77229b0064\WCFWebApi\Http\Src\Microsoft.ApplicationServer.Http\Microsoft\ApplicationServer\Http\Channels\HttpMessageEncodingRequestContext.cs:line 99 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc) InnerException: System.InvalidOperationException Message=The type System.Data.Entity.DynamicProxies.Parent_8F972B3EEED726B14DD4589EF146B9CA4312CDAC9E8E7B4FE8C3FB1436DA1806 was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically. Source=3nsisc24 StackTrace: at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write3_Parent(String n, String ns, Parent o, Boolean isNullable, Boolean needType) at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterDelegatingEnumerable1.Write4_ArrayOfParent(Object o) InnerException: Here is my complete code (excluding Global.asax.cs):using System.Collections.ObjectModel;using System.ComponentModel.DataAnnotations;using System.Data.Entity;using System.Linq;using System.ServiceModel;using System.ServiceModel.Web;namespace WebApplication1{ [ServiceContract] public class TestApi { [WebGet( UriTemplate = "" )] public IQueryable<Parent> Get() { var db = new DataContext(); var jobs = db.Parents .Include( "Children" ); return jobs; } } public class DataContext : DbContext { public DbSet<Parent> Parents { get; set; } } public class Parent { [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )] public int Id { get; set; } public string Name { get; set; } public int Number { get; set; } public virtual Collection<Child> Children { get; set; } } public class Child { [Key, DatabaseGenerated( DatabaseGeneratedOption.Identity )] public int Id { get; set; } public string Name { get; set; } }}
>
>
Sep 23, 2011 at 6:52 AM

Unfortunately the JSON serializer didn't work either, however the problem was solved by putting the following line in the constructor in the DbContext

Configuration.ProxyCreationEnabled = false;

Coordinator
Sep 23, 2011 at 7:37 AM

If you do that I believe you are losing some of the lazy loading / entity tracking features of EF. The whole point for the proxying is to allow you to pass a POCO and then have the necessary infra stuff like for lazy loading, pushed in at runtime.

Sep 23, 2011 at 11:59 AM

DTO+Automapper = butter. Plus you don't tie your api to the model

Sep 23, 2011 at 1:01 PM
use Json.NET serializer. That's what we are using, with proxy creation and lazy loading, and it works like a charm.

http://nuget.org/List/Packages/netfx-WebApi.JsonNetFormatter

/kzu

--
Daniel Cazzulino | Developer Lead | MS MVP | Clarius Consulting | +1 425.329.3471


On Fri, Sep 23, 2011 at 02:52, gregbacchus <notifications@codeplex.com> wrote:

From: gregbacchus

Unfortunately the JSON serializer didn't work either, however the problem was solved by putting the following line in the constructor in the DbContext

Configuration.ProxyCreationEnabled = false;

Read the full discussion online.

To add a post to this discussion, reply to this email (wcf@discussions.codeplex.com)

To start a new discussion for this project, email wcf@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Sep 26, 2011 at 1:25 AM
gblock wrote:

If you do that I believe you are losing some of the lazy loading / entity tracking features of EF. The whole point for the proxying is to allow you to pass a POCO and then have the necessary infra stuff like for lazy loading, pushed in at runtime.

Yes, I am aware that. But to me, the concept of lazy loading doesn't make sense when you are serializing something anyway.

Sep 26, 2011 at 1:34 AM
ssuing8825 wrote:

DTO+Automapper = butter. Plus you don't tie your api to the model

I like the sound of this, but will it be able to automatically map the query expression tree across too?

The whole point of what I am trying to achieve is utilize the fact that with WCF if you return an IQueryable type, then you can add query elements to the request URL. Couple this with EF where the IQueryable is evaluated on the SQL server (rather than in CLR). So, say I have 1,000,000 rows and I want to sort by an arbitrary value and then take the to 10 rows, the "order by" and the "top" are done on the SQL server, returning me only 10 rows. If the expression tree is not mapped, the SQL server would have to return me all rows, for them to be sorted by LINQ.

Sep 26, 2011 at 5:46 PM

I have a similar problem.  I solved it in two ways:

1)  Ensure my domain model has NO REFERENCE LOOPS, this goes against the 'make things nicely reachable' rule in your domain model, but does solve an issue with generating 'help' text in the testing tool, and probably isn't necessary for solving the problem.

2)  Made a custom build of the JsonMediaTypeFormatter class that includes all known-assignable types at runtime based on reflection.  The basic implementation could probably be improved for performance reasons, but since all the 'normal' methods of assigning data contract resolvers for WCF do not work OOB, and the built-in JSON data serializer doesn't support DataContractResolver [apparently], it seemed the quickest method.  The result looks like this:

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is propagated.")]
        private static DataContractJsonSerializer CreateDefaultSerializer(Type type)
        {
            Contract.Assert(type != null, "type cannot be null.");
            DataContractJsonSerializer serializer = null;

            try
            {
                //// TODO: CSDMAIN 211321 -- determine the correct algorithm to know what is serializable.
                serializer = IsKnownUnserializableType(type) ? null : new DataContractJsonSerializer(type, GetKnownTypes(type));
            }
            catch (Exception)
            {
                //// TODO: CSDMain 232171 -- review and fix swallowed exception
            }

            return serializer;
        }

        private static Type[] GetKnownTypes(Type type)
        {
            List<Type> knownTypes = new List<Type>();
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type asmType in asm.GetTypes())
                {
                    if (type.IsAssignableFrom(asmType))
                    {
                        knownTypes.Add(asmType);
                    }
                }
            }

            return knownTypes.ToArray();
        }

This solved the issue for me [at least as far down this path as I've gotten], but it would be nice to have this support built into the WebAPI so we don't need to worry about these type of things moving forward...

Coordinator
Sep 27, 2011 at 8:23 AM

TheJet you can override the serializer we use within the JsonMediaTypeFormatter. if you look at the formatter it exposes a method you can call to set the underlying serializer with known types.

Oct 19, 2011 at 4:37 AM

For anyone who is interested in what I did:

I used a custom MediaTypeFormatter for both XML and JSON (still using DataContractSerializer) and user a IDataContractSurrogate that in turn used AutoMapper to convert the DTOs to DataContracts.

Dec 25, 2011 at 2:22 PM

I am also getting the same issues. While returning a POCO created using EF T4 template, I am getting the 'MediaFormatter' issue. Can you please let me know that where could I download the code of
"custom MediaTypeFormatter  XML and JSON" . Also, please guide me that how to use those.

I am getting the following exception:
"Cannot serialize member Models.Customer.Orders of type System.Collections.Generic.ICollection`1[[Models.Order, Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface. "

Following is the POCO class

[Serializable]
[DataContract(IsReference = true)]
[KnownType(typeof(Order))]
public partial class Customer
{
   
#region Primitive Properties
   
[DataMember]
   
public virtual int CustomerID { get; set; }

   
[DataMember]
   
public virtual string CustomerCode { get; set; }

   
[DataMember]
   
public virtual string Description  { get; set; }

   
[DataMember]
   
public virtual string Comments { get; set; }

   
[DataMember]
   
public virtual bool DeleteFlag { get; set; }

   
[DataMember]
   
public virtual byte[] RowVersion { get; set; }
   
#endregion
   
#region Navigation Properties

   
[DataMember]
   
public virtual ICollection<Order> Orders
   
{
       
get
       
{
           
if (_order == null)
           
{
               
var newCollection = new FixupCollection<Order>();
                newCollection
.CollectionChanged += FixupOrders;
                _order
= newCollection;
           
}
           
return _order;
       
}
       
set
       
{
           
if (!ReferenceEquals(_order, value))
           
{
               
var previousValue = _order as FixupCollection<Order>;
               
if (previousValue != null)
               
{
                    previousValue
.CollectionChanged -= FixupOrders;
               
}
                _order
= value;
               
var newValue = value as FixupCollection<Order>;
               
if (newValue != null)
               
{
                    newValue
.CollectionChanged += FixupOrders;
               
}
           
}
       
}
   
}
   
private ICollection<Order> _order;

   
#endregion
   
#region Association Fixup

   
private void FixupOrders(object sender, NotifyCollectionChangedEventArgs e)
   
{
       
if (e.NewItems != null)
       
{
           
foreach (Order item in e.NewItems)
           
{
                item
.Customer = this;
           
}
       
}

       
if (e.OldItems != null)
       
{
           
foreach (Order item in e.OldItems)
           
{
               
if (ReferenceEquals(item.Customer, this))
               
{
                    item
.Customer = null;
               
}
           
}
       
}
   
}

   
#endregion
}

The method in service is as follows:

[WebGet(UriTemplate = "Customer",
       
RequestFormat = WebMessageFormat.Xml,
       
ResponseFormat = WebMessageFormat.Xml)]
public List<Customer> Get()
{
   
CustEntities context = new CustEntities();
   
return context.Customer.Include("Orders").ToList();
}


Thanks

 

Dec 28, 2011 at 5:52 AM

How is the WCF RIA Services framework able to serialize to JSON without this problem?

Dec 28, 2011 at 8:16 AM

Try turning off proxy creation on your EF objects...

 

    public class YourDataContext : DbContext
    {

        public YourDataContext(string connectionString) : base(connectionString) 
        {
            this.Configuration.ProxyCreationEnabled = false;
        }


        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
        }
    }