Serializable classes and dynamic proxies in EF - how?

35,356

Solution 1

If you want to serialize entities you can disable proxy creation before retrieving that object. You also need to eager load navigational properties if you want to serialize them as well.

To disable proxy creation in EF 4.1

dbContext.Configuration.ProxyCreationEnabled = false;

In EF 4

objectContext.ContextOptions.ProxyCreationEnabled = false;

eg:

var users = context.Users.Include("Claims").Where(/**/);

Solution 2

Look into T4 Templates for Entity Framework, you can control how EF generates your entities, you will have to define that they are serializable in the T4 Template.

Solution 3

Turn off lazy loading and turn off proxy class creation. Anyway you still need to add the Serializable/DataContract attributes in order to make it serializable.

Share:
35,356

Related videos on Youtube

ekkis
Author by

ekkis

generally frustrated with my own intellect. a leech at stackoverflow.

Updated on July 25, 2020

Comments

  • ekkis
    ekkis almost 4 years

    In [a previous posting], I was set on the path to having to clone my entities. This I've attempted to do with a serialisation approach as found in [codeproject].

    because the classes are generated by Entity Framework, I mark them up separately in a custom .cs like this:

    [Serializable]
    public partial class Claims
    {
    }
    

    however, when the check (in the clone method):

    if (Object.ReferenceEquals(source, null))
    {
    

    gets hit, I get the error:

    System.ArgumentException was unhandled by user code
      Message=The type must be serializable.
    Parameter name: source
      Source=Web
      ParamName=source
      StackTrace:
           at .Web.Cloner.Clone[T](T source) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Extensions.Object.cs:line 49
           at .Web.Models.Employer..ctor(User u) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Models\EF.Custom.cs:line 121
           at .Web.Controllers.AuthController.Register(String Company, String GivenName, String Surname, String Title, String Department) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Controllers\AuthController.cs:line 119
           at lambda_method(Closure , ControllerBase , Object[] )
           at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
           at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
           at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
           at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
           at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
      InnerException: 
    

    so apparently whilst my class Claims is serialisable, the dynamic proxies generated by EF are not... somehow my decorations are not flowing through.

    what's the trick here?

    * Update I *

    for more context: I have a class User which contains a property Claims defined as an ICollection<Claim>. when doing the cloning, the type that gets passed is the collection, not Claim - this explains why the cloner is complaining that the type is not serializable. so the question now is: how do I make User.Claims serializable since I can't decorate a property?

    Error   1   Attribute 'Serializable' is not valid on this declaration type.
    It is only valid on 'class, struct, enum, delegate' declarations.   
    C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Models\EF.Custom.cs
    128 10  Website
    

    * Update II *

    the point of the exercise is to facility a deep copy. this is what it looks like:

    public partial class Employer
    {
        public Employer(User u)
        {
            this.Id = u.Id;
            this.GivenName = u.GivenName;
            this.Surname = u.Surname;
            this.Claims = u.Claims.Clone();
            this.Contacts = u.Contacts.Clone();
        }
    }
    

    in order for the u.Claims.Clone() to work, u.Claims must be serializable but it's not for the reasons cited above.

    * Update III *

    ok, I changed approach, implementing the constructor like this:

    public partial class Employer
    {
        public Employer(User u)
        {
            this.Id = u.Id;
            this.GivenName = u.GivenName;
            this.Surname = u.Surname;
    
            ICollection<Claim> cs = new List<Claim>();
            foreach (Claim c in u.Claims)
            {
                cs.Add(c.Clone());
            }
            this.Claims = cs;
    

    and now it gets past the clone()'s check ("if" line above), but now it breaks at:

    formatter.Serialize(stream, source);
    

    with:

    System.Runtime.Serialization.SerializationException was unhandled by user code
      Message=Type 'System.Data.Entity.DynamicProxies.User_7B7AFFFE306AB2E39C07D91CC157792F503F36DFCAB490FB3333A52EA1D5DC0D' in Assembly 'EntityFrameworkDynamicProxies-Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
      Source=mscorlib
      StackTrace:
           at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
           at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
           at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
           at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
           at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
           at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
           at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
           at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph)
           at Skillscore.Web.Cloner.Clone[T](T source) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Extensions.Object.cs:line 62
           at Skillscore.Web.Models.Employer..ctor(User u) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Models\EF.Custom.cs:line 130
    

    sigh... is everything always so hard?

    * Update IV *

    ok, so the problem above is that the Claim class has a navigator that points back to User - which explains why the above method indicates the type to be .User_[...] and implies I need to not only make the downward dependencies serializable, but also all of the paths back up! However, having done that I successfully clone the object but I'm now back to the issue in my original posting:

    System.InvalidOperationException was unhandled by user code
      Message=Conflicting changes to the role 'User' of the relationship 'EF.ClaimUser' have been detected.
      Source=System.Data.Entity
      StackTrace:
           at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)
           at System.Data.Objects.DataClasses.EntityCollection`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)
           at System.Data.Objects.DataClasses.RelationshipManager.AddRelatedEntitiesToObjectStateManager(Boolean doAttach)
           at System.Data.Objects.ObjectContext.AddObject(String entitySetName, Object entity)
           at System.Data.Entity.Internal.Linq.InternalSet`1.<>c__DisplayClass5.<Add>b__4()
           at System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
           at System.Data.Entity.Internal.Linq.InternalSet`1.Add(Object entity)
           at System.Data.Entity.DbSet`1.Add(TEntity entity)
           at Skillscore.Web.Controllers.AuthController.Register(String Company, String GivenName, String Surname, String Title, String Department) in C:\Users\.\Documents\Visual Studio 2010\Projects\.\Website\Controllers\AuthController.cs:line 138
    

    man. I need a hole in the head.

    * Update V *

    I don't know if the issue is the proxies or lazy loading, but after thinking about it a little, it seems that if I do a clone via serialisation, all the IDs for things that used to belong to the old object are now going to belong to the new one. I did do a .remove() first on the old object and if that has immediate effect then maybe there's something in the tracking that doesn't know about it. If it doesn't, then at one point there will be two things out there with the same ID... so I'm starting to lean towards @Jockey's idea of using object initialisers for the cloning...

    • Cubicle.Jockey
      Cubicle.Jockey over 12 years
      Is user generated by EF?
    • ekkis
      ekkis over 12 years
      yes, and so is Employer and Claim
    • ekkis
      ekkis over 12 years
      I think the answer's going to be trawling through the collections by hand and deeply copying the elements in them...
    • ekkis
      ekkis over 12 years
      @Jockey, it was a step in the right direction (see Update III) but alas I'm not there yet
    • Cubicle.Jockey
      Cubicle.Jockey over 12 years
      Instead of .Clone() try and object intializer and reset all the properties..lame I know but might work. cs.Add(new Claim { Property = c.Property })
    • ekkis
      ekkis over 12 years
      @Jockey, solved the issue of cloning but I got into this whole mess because I thought EF was getting confused by shallow copies where a reference to an object belonging to another was getting fiddled under its nose. so I figured, a deep copy will solve that because it's a new object. I guess I was wrong and now I don't know why I can't add the new object.
    • reggaeguitar
      reggaeguitar over 9 years
      @ekkis I'm curious what happened with this? I'm trying to implement the same thing myself and I ended up turning off lazy loading, did you come up with a better alternative?
  • Cubicle.Jockey
    Cubicle.Jockey over 12 years
    Here is a place to start. matthidinger.com/archive/2010/04/22/…
  • ekkis
    ekkis over 12 years
    Jockey, thanks for the suggestion. it's certainly interesting but I don't see how it helps me - see my update above.
  • Cubicle.Jockey
    Cubicle.Jockey over 12 years
    Which proxy that is generated specifically. If you use the T4 Templates you can control everything about how the dynamically generated entities are created, so you would in that Template go defined those proxies or anything else you need to be serializable.? So I used T4 templates for my self to make the generated items from EF for example to be serializable and POCO style to be sent of WCF serialization.
  • ekkis
    ekkis over 12 years
    @Jockey, yes, I get that. the problem is I don't know the correct syntax to indicate that a property needs to be serializable... in other words when doing my deep copy, I don't want to make User serializable but I do need to make sure Claims get deeply copied. I've included the deep-copy code in a second edit above.
  • Travis J
    Travis J almost 12 years
    Will disabling proxy creation mean that all fields must be queried explicitly?
  • Travis J
    Travis J almost 12 years
    I was forced to disable ProxyCreation in order to make [ScriptIgnore] work. It seems though that I do not have to specify each field explicitly. If I var q = context.Set<TEntity>(); it will still give me all fields. For virtual relations, additional Includes must be used. What aspect of lazy loading will not work with ProxyCreation disabled?
  • Eranga
    Eranga almost 12 years
    @TravisJ EF will not be able to lazy load the navigation properties if you disable proxy creation.
  • TechQuery
    TechQuery over 8 years
    @Eranga , i have one question. I didnt disable ProxyCreation and i just disabled Lazyloading alone. Serialization works fine for json but not for Xml and its complaining abt the proxy stuff. If dynamic proxies cannot be serialized, how come it work for json?
  • Matt
    Matt over 7 years
    another person answered the same way 4 years previous and the OP said it didn't help.
  • Mselmi Ali
    Mselmi Ali almost 5 years
    @ekkis, does my answer was helpful, still have issues or does this resolve your issues ?