Question:
Is it possible when dealing with dynamic proxy to pull out the underlying POCO when we need to serialize them?
Rationale:
I have a requirement to serialize (XML) my POCO entities using EF Code First but quickly found that DbContext creates dynamic proxy for my POCO and that makes it hard to serialize.
I have tried the following:
Disable proxy creation in the DbContext and work only with pure POCO. This allows me to serialize the instances any way I like. The only catch is that navigational properties are not being tracked, therefore, I must attach all related entities manually when I want to save otherwise new entities will always be created (see code example).
Implement ISerializable interface on the POCO to manually handle serialization. This is a lot of work and is not a sustainable solution.
Code example.
// Attach and update tags
foreach (var tag in entity.Tags)
{
Context.Entry(tag).State = Context.Tags.Any(t => t.ID == tag.ID)
? EntityState.Modified
: EntityState.Added;
}
// Attach and update state.
Context.Entry(entity).State = Context.Resources.Any(x => x.ID == entity.ID)
? EntityState.Modified
: EntityState.Added;
As you can imagine, the complexity can get out of hand when my entity have more relationships.
Is it possible when dealing with dynamic proxy to pull out the
underlying POCO when we need to serialize them?
No because there is no underlying POCO - the proxy is not a wrapper around entity instance. It is entity instance directly.
You can use DataContractSerializer and ProxyDataContractResolver to serialize proxied POCOs but serializing proxied entities sounds like you are trying to serialize entities with lazy loading enabled - that can serialize much more than you expect because every property will be lazy loaded recursively until there is no single non-loaded navigation property in the whole object graph.
You must also handle circular references when using DataContractSerializer by marking your entities with [DataContract(IsReference = true)] and every serializable property with [DataMember].
The only catch is that navigational properties are not being tracked
Entities without proxy are tracked as well. The tracking is dependent on entity being attached not entity being proxied.
I must attach all related entities manually when I want to save
You must always attach deserialized entities if you want to persist them.
Related
I'm using Entity Framework. I've attached a POCO object representing an entity in the DB to my dbcontext using:
var entity = new MyEntity() { ID = 1, AnotherItemID = 10 };
context.Set<T>().Attach(entity);
So far so good. I can access the set and work with the entity I've added. It's added in the Unchanged state. However, it is only a POCO and not a Proxy. Therefore, when I try to access a navigation property, e.g. myEntity.AnotherItem, I just get a null back.
Does anyone know if there is a way to have EF resolve navigation properties for POCO classes attached in this way? Or of a way to cast the POCO to a proxy class?
Thanks
Update
There are two ways to solve this (of course there may be others too!). One is the Explicit Loading option in the answer below. The other way, which allows lazy loading to work, is to use the DBSet Create method rather than the POCO new keyword when creating entities to be attached. More info about that here:
EF4.3 Code-First, MVC, Lazy Loading After Attaching in POST Action
You can use Explicity Loading:
//When you want to load a reference navigation property
context.Entry(entity).Reference(p => p.AnotherItem).Load();
//When you want to load a collection navigation property
context.Entry(post).Collection(p => p.Items).Load();
For auditing/history purposes, I am using the Entity Framework change tracker to determine, before writing changes, what has changed and serialize the changes. I can get the changed entities by calling this.ChangeTracker.Entries() in my DbContext derivative and looking at the values for anything marked EntityState.Added, EntityState.Deleted, or EntityState.Modified. This all works great.
My problem is that this method does not work to track changes to collections of EF objects (for instance, an ICollection<Person> property on a PersonGroup object).
I'm sure the EF context must track this somehow -- how else would the database update work, after all? But is it available to me?
What you're looking for is relationship change tracking. You can find it in ObjectStateManager of the underlying ObjectContext, here is how you get all added relationships:
//you need to call DetectChanges
((IObjectContextAdapter)context).ObjectContext.DetectChanges();
var addedRelations = ((IObjectContextAdapter)context).ObjectContext
.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e=>e.IsRelationship).ToList();
It turns out you can get at the relationships with this code (assuming it's running inside your DbContext derivative):
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager
.GetObjectStateEntries(EntityState.Added)
.Where(e => e.IsRelationship)
.Select(r => new {EntityKeyInfo = r.CurrentValues[0],
CollectionMemberKeyInfo = r.CurrentValues[1], r.State});
Obviously you can tweak this based on what you need and it's up to do you something useful with it. The first two CurrentValues entries represent EntityKey objects which will allow you to get the IDs of the entities in question.
If you want to deal with deleted entities this won't work and you need to use reflection. Instead of CurrentValues[0] and CurrentValues[1] you can look at the internal properties Key0 and Key1, which are defined in an internal class you can't access at compile time. This will work: r.GetType().GetProperty("Key0", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(r, new object[0]). Note that this is probably not an intended use and could blow up whenever.
I have a context class, and at some point I need to get data from database in my POCO classes, so that I can serialize that data and send over to my web service. I do not want to deserialize proxies on the other end, but I am not able to force EF to create my POCO classes.
I am using following code to retrieve data:
((IObjectContextAdapter) this).ObjectContext.ContextOptions.ProxyCreationEnabled = false;
var nodes = (from node in TreeNodes select node).ToList();
((IObjectContextAdapter)this).ObjectContext.ContextOptions.ProxyCreationEnabled = true;
Note that TreeNodes is a TPH and base class is abstract. Is there a way to get POCO classes in my case?
I solved this scenario by instantiating same context again, settings configuration, and then run the query and disposing context.
I have an entity model with a self relation (parent/child). My entity named Article has a property called parent. This parent is in fact the relation, and ParentID which is the field in the relation. In ef 4 i did this:
using (var dbContext= new DataBaseModel())
{
ArticleTable newEntity= new ArticleTable();
newEntity.name="childArt";
newEntity.ParentID = 1;
dbContext.ArticleTable.Add(newEntity);
dbContext.SaveChanges();
//after calling save I can do this
var parentName = newEntity.Parent.Name;
}
With entity framework 6, this doesn't work any more, I have get the entity from the database again in order to get the related parent entity. Is this because of changes to lazyloading? what should i do different.
The difference is that in EF 4 entities were generated with piles of code that took care of change notification and lazy loading. Since then, the DbContext API with POCOs has become the standard.
If you want the same behavior as with the old 'enriched' entities you must make sure that lazy loading can occur by a number of conditions:
The context must allow lazy loading. It does this by default, but you can turn it off.
The navigation properties you want to lazy load must have the virtual modifier, because
EF must create dynamic proxies. These proxies somewhat resemble the old generated entities in that they are able to execute lazy loading, because they override virtual members of the original classes.
The last (and maybe second) point is probably the only thing for you to pay attention to. If you create a new object by new, it's just a POCO object that can't do lazy loading. However, if you'd create a proxy instead, lazy loading would occur. Fortunately, there is an easy way to create a proxy:
ArticleTable newEntity= dbContext.ArticleTables.Create();
DbSet<T>.Create() creates dynamic proxies -
if the underlying context is configured to create proxies and the entity type meets the requirements for creating a proxy.
Is there a way to materialize an instance of your own class that derives from the entity type specified in your model? Let's say I have a "ClassName" property in my model for a given entity and whenever EF materializes an instance of the entity, I want to create an instance of the specified ClassName instead (which is a sub-class of the entity of course).
I know there is the ObjectMaterialized event on the ObjectContext but at this point the entity is already created.
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
objectContext.ObjectMaterialized += OnObjectMaterialized;
I need a hook right before this to customize the object creation. You might ask why on earth would I need to do this. I have a large object model that changes constantly so I can't define all my classes in the EDMX. The properties that I store in the database can be abstracted to a couple classes at the top of the object model hierarchy. I intend to create entities for those using table-per-type inheritance. But if I could retrieve specific object instances directly when using EF that would be awesome.
Thanks!
I wanted to create custom proxys and debugged deep into the entity framework. I found the translator class, which is responsible to create the entities by reflection. The namespace of the class is System.Data.Entity.Core.Common.Internal.Materialization.
The method creating the entites is the private method Emit_ConstructEntity. The entity type is a parameter and is created by the private method LookupObjectMapping based on an EdmType object (I was using model first).
If you want to change the class, which are created, you have to create a custom MetaDataWorkspace. The workspace is responsible to select the EdmType. Maybe try to debug into the methods yourself and have a look how the MetaDataWorkspace is used to identify the CLR type.