EF4 Cast DynamicProxies到底层对象

我正在使用带有POCO模板的Entity Framework 4。

我有一个List,其中MyObject是动态代理。 我想使用XmlSerializer序列化此列表,但我不希望它们序列化为DynamicProxies,而是作为底层POCO对象。

我知道ContextOptions.ProxyCreationEnabled,但我不想使用它。 我只是想知道如何将代理对象转换为它的POCO序列化。

今天面临同样的问题,并使用Value Injecter来解决它。 它很简单:

var dynamicProxyMember = _repository.FindOne(m=>m.Id = 1); var member = new Member().InjectFrom(dynamicProxyMember) as Member; 

我通过提供帮助我的解决方案来挖掘这些旧骨头。 希望它能帮助读它的人。

所以,实际上有两个解决方案。 如果您不想延迟加载,您可以随时关闭动态代理,这将只为您提供权限:

 public class MyContext : DbContext { public MyContext() { this.Configuration.ProxyCreationEnabled = false } public DbSet NiceCats {get; set;} public DbSet CrazyCats {get; set;} public DbSet MeanCats {get; set;} } 

另一种解决方案是使用ObjectContext获取代理所代表的原始实体类型:

 using (var db = new MyContext()) { var meanAssCat = context.MeanCats.Find(CurrentCat.Id) var entityType = ObjectContext.GetObjectType(meanAssCat.GetType()); } 

由于您不希望关闭ProxyCreation,因此只要将virtual关键字放入object属性(EF Contextinheritance您的对象并使用DynamicProxy对象替换虚拟属性),就会陷入DynamicProxy对象。 这些DynamicProxy对象不会从您的POCO实体inheritance,它们只具有相同的属性,可以用来代替您的POCO。 如果你真的必须转换为POCO对象(我不相信有人会想出一种方法来构建它),你可以尝试通过编写复制构造函数来解决这个问题,复制构造函数将从传递的参数中复制所有属性(不是很聪明)从性能的角度来看,但你必须做什么,你必须做什么),或者在父对象中使用System.Xml.Serialization.XmlTypeAttribute ,它包含你的动态代理而不是poco告诉序列化器如何序列化虚拟属性(进入哪种类型) )。

免责声明:我已经为这个问题创建了一个通用的解决方案。 我在寻找解决方案时发现了这个老问题,所以我想我会在这里分享我的解决方案,以帮助那些可能在同一问题上纠缠他或她的脚趾的人。

我遇到了同样的问题:我需要从Entity Framework获取一些东西,然后使用ASP.NET Web Api将其序列化为XML。 我已经尝试禁用延迟加载和代理创建以及使用Include(),但除了最基本的类层次结构之外的任何事情都会导致需要几分钟才能执行的巨大SQL查询。 我发现使用延迟加载并递归地引用每个属性比一次加载树快很多倍,所以我想我需要一种方法来延迟加载所有内容,以POCO的forms获取它,然后序列化它。

我用Gert Arnold的这个答案作为这个解决方案的基础,然后从那里开始工作。

我在DBContext中创建了一个Unproxy方法,该方法接受一个(代理的)类实例(例如你从DbContext.Find(id)返回的东西)并将该实体作为一个实际的POCO类型返回,每个属性为sub -property等完全加载并准备进行序列化。

Unproxy方法和一些只读字段:

 readonly Type ignoreOnUnproxyAttributeType = typeof(IgnoreOnUnproxyAttribute); readonly string genericCollectionTypeName = typeof(ICollection<>).Name; public T UnProxy(T proxyObject) where T : class { // Remember the proxyCreationEnabled value var proxyCreationEnabled = Configuration.ProxyCreationEnabled; try { Configuration.ProxyCreationEnabled = false; T poco = Entry(proxyObject).CurrentValues.ToObject() as T; // Convert the proxy object to a POCO object. This only populates scalar values and such, so we have to load other properties separately. // Iterate through all properties in the POCO type foreach (var property in poco.GetType().GetProperties()) { // To prevent cycles, like when a child instance refers to its parent and the parent refers to its child, we'll ignore any properties decorated with a custom IgnoreOnUnproxyAttribute. if (Attribute.IsDefined(property, ignoreOnUnproxyAttributeType)) { property.SetValue(poco, null); continue; } dynamic proxyPropertyValue = property.GetValue(proxyObject); // Get the property's value from the proxy object if (proxyPropertyValue != null) { // If the property is a collection, get each item in the collection and set the value of the property to a new collection containing those items. if (property.PropertyType.IsGenericType && property.PropertyType.Name == genericCollectionTypeName) { SetCollectionPropertyOnPoco(poco, property, proxyPropertyValue); } else { // If the property is not a collection, just set the value of the POCO object to the unproxied (if necessary) value of the proxy object's property. if (proxyPropertyValue != null) { // If the type of the property is one of the types in your model, the value needs to be unproxied first. Otherwise, just set the value as is. var unproxiedValue = (ModelTypeNames.Contains(property.PropertyType.Name)) ? SafeUnproxy(proxyPropertyValue) : proxyPropertyValue; property.SetValue(poco, unproxiedValue); } } } } return poco; // Return the unproxied object } finally { // Zet ProxyCreationEnabled weer terug naar de oorspronkelijke waarde. Configuration.ProxyCreationEnabled = proxyCreationEnabled; } } 

ModelTypeNames是我添加到我的DBContext的属性,它只返回模型中使用的所有类型。 这样我们就可以知道我们需要解除哪些类型:

 private Collection modelTypeNames; private Collection ModelTypeNames { get { if (modelTypeNames == null) { // We'll figure out all the EF model types by simply returning all the type arguments of every DbSet<> property in the dbContext. modelTypeNames = new Collection(typeof(VerhaalLokaalDbContext).GetProperties().Where(d => d.PropertyType.Name == typeof(DbSet<>).Name).SelectMany(d => d.PropertyType.GenericTypeArguments).Select(t => t.Name).ToList()); } return modelTypeNames; } } 

要处理ICollection <>属性,我们需要首先实例化一个新的泛型集合(我使用反射创建一个带有正确类型参数的HashSet <>),遍历所有值,取消每个值的代理并将其添加到新的HashSet,然后用作POCO属性的值。

 private void SetCollectionPropertyOnPoco(T poco, PropertyInfo property, dynamic proxyPropertyValue) where T : class { // Create a HashSet<> with the correct type var genericTypeArguments = ((System.Type)(proxyPropertyValue.GetType())).GenericTypeArguments; var hashSetType = typeof(System.Collections.Generic.HashSet<>).MakeGenericType(genericTypeArguments); var hashSet = Activator.CreateInstance(hashSetType); // Iterate through each item in the collection, unproxy it, and add it to the hashset. foreach (var item in proxyPropertyValue) { object unproxiedValue = SafeUnproxy(item); hashSetType.GetMethod("Add").Invoke(hashSet, new[] { unproxiedValue }); // Add the unproxied value to the new hashset } property.SetValue(poco, hashSet); // Set the new hashset as the poco property value. } 

请注意,我正在调用SafeUnproxy而不是Unproxy。 这是因为类型推断存在一个奇怪的问题。 通常当您将代理对象传递给Unproxy()时,类型推断将推断T是您实际需要的POCO类型,而不是数据混合的类型(看起来像YourModelPocoType_D0339E043A5559D04303M3033等)。 然而,偶尔它确实推断T为数据混合类型,这会炸毁

 T poco = Entry(proxyObject).CurrentValues.ToObject() as T; 

line,因为poco对象无法强制转换为代理类型,导致as运算符返回null。 为了解决这个问题,SafeUnproxy使用显式类型参数调用Unproxy方法,而不是依赖于推理:它检查传递它的参数的类型,如果命名空间是System.Data.Entity.DynamicProxies,它将使用类型的BaseType(在dynamicproxy类型的情况下是对应的POCO类型)作为generics类型参数。

 private object SafeUnproxy(dynamic item) { // ProxyCreation is off, so any reference or collection properties may not yet be loaded. We need to make sure we explicitly load each property from the db first. ExplicitlyLoadMembers(item); // Figure out the right type to use as the explicit generic type argument var itemType = item.GetType(); Type requiredPocoType = (itemType.Namespace == "System.Data.Entity.DynamicProxies") ? itemType.BaseType : itemType; // Call Unproxy using an explicit generic type argument var unproxiedValue = typeof(VerhaalLokaalDbContext).GetMethod("UnProxy").MakeGenericMethod(requiredPocoType).Invoke(this, new[] { item }); return unproxiedValue; } 

确保从数据库加载每个属性是迭代对象的属性并检查IsLoaded的问题:

 private void ExplicitlyLoadMembers(dynamic item) { foreach (var property in ((Type)item.GetType()).GetProperties()) { DbEntityEntry dbEntityEntry = Entry(item); var dbMemberEntry = dbEntityEntry.Member(property.Name); // If we're dealing with a Reference or Collection entity, explicitly load the properties if necessary. if (dbMemberEntry is DbReferenceEntry) { if (!dbEntityEntry.Reference(property.Name).IsLoaded) { dbEntityEntry.Reference(property.Name).Load(); } } else if (dbMemberEntry is DbCollectionEntry) { if (!dbEntityEntry.Collection(property.Name).IsLoaded) { dbEntityEntry.Collection(property.Name).Load(); } } } } 

最后,IgnoreOnUnproxyAttribute用于避免循环:

 [System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] sealed class IgnoreOnUnproxyAttribute : Attribute { } 

用法如下:

 MyDbContext db = new MyDbContext(); public Story Get(int storyId) { var lazyStory = db.Stories.SingleOrDefault(s => s.Id == storyId); var unproxied = db.UnProxy(lazyStory); return unproxied; } 

由于所有reflection都在进行,性能并不引人注目,但是执行时间平均只比延迟加载实体,迭代其所有属性,然后序列化dynamicproxy本身时要稍微(即不到一秒)。 而且,它比使用Include()快得多,速度非常慢且容易出错。

希望它对某人有帮助。

我在EF 5中遇到了同样的问题。我试图将我的实体对象序列化为XML。 @Koreyam的回答给了我一个提示。 我开发了一点点。 在我的代码中,我正在调用这样的序列化器

 string objXML = EntitySerializer.Serialize(entity); 

Serialize方法是通用的。 所以方法头是这样的:

 public static string Serialize(T tObj) where T : class, new() 

所以在我的方法体中我使用值injecter :

 T obj = new T().InjectFrom(tObj) as T; 

它刚刚为我的所有权利解决了我的问题。