【问题标题】:How can I convert a dynamic proxy into a POCO?如何将动态代理转换为 POCO?
【发布时间】:2012-07-06 04:18:47
【问题描述】:

我在尝试序列化域模型时遇到了需要将动态代理转换为 POCO 的问题。我遇到的问题是模型中的虚拟属性存在循环引用。尽管我尝试使用[ScriptIgnore] 来让序列化程序不解析这些属性,但它仍然可以。我相信这是因为对象是动态代理,并且属性中仍然存在一些残余物导致解析器进入(这反过来导致递归错误“循环引用” - 我尝试将递归限制为 3 个步骤,但我得到了“超出递归步骤”的错误)。

如何将对象从动态代理转换为 POCO,以便对其进行序列化?

编辑:简单示例

public class One : BaseViewModel
{
    public int OneId { get; set; }
    public virtual ICollection<Two> Two { get; set; }
}

public class Two
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
    [ScriptIgnore]
    public virtual One One { get; set; }
}

public abstract class BaseViewModel
{
    public string AsJson()
    {
        var serializer = new JavaScriptSerializer();
        return serializer.Serialize(this);
    }
}

【问题讨论】:

  • 代理是它们所代表的 POCO 的子类。通常,您应该能够很好地序列化它们。你能发布一个你无法序列化的类的小而完整的例子吗?
  • @EricJ。 - 正常构造时,类序列化。我可以发布一个示例,但我不确定它会有多大帮助,因为运行时它会运行得很好。主要问题是当使用来自 ObjectContext 的数据实例化类时。这是当虚拟属性内部仍然存在引用时,即使它们应该为空,因为它们并未全部包含在对数据库的查询中。
  • 查看给您带来问题的类的结构可能会有所启发。
  • @EricJ。 - 这是循环引用。
  • 接受的答案解决了序列化问题,但here 是您原始问题的答案。

标签: c# asp.net-mvc-3 serialization entity-framework-4.1 dynamic-proxy


【解决方案1】:

作为 Jim 的 Automapper 解决方案的替代方案,我在将 POCO 代理“映射”(浅复制)到同一 POCO 的实例方面取得了一些成功。执行此操作的简单方法是修改生成 POCO 的模板文件以包含 ToSerializable() 方法,因此 POCO 类如下所示:

public partial class cfgCountry
    {
        public cfgCountry()
        {
            this.People = new HashSet<Person>();
        }

        [Key]
        public int CountryID { get; set; }
        public string Name { get; set; }
        public int Ordinal { get; set; }

        public virtual ICollection<Person> People { get; set; }

        public cfgCountry ToSerializable()
        {
            return new cfgCountry()
            {
            CountryID = this.CountryID,
            Name = this.Name,
            Ordinal = this.Ordinal,
            };
        }
    }

这是我添加到 POCO 模板 (tt) 文件中以创建 ToSerializable 函数的函数(这种语法太丑了。):

<#+
void WriteToSerializableMethod (CodeGenerationTools code, IEnumerable<EdmProperty> primitiveProperties, EntityType entity)
{

#>
public <#=code.Escape(entity)#> ToSerializable()
{
    return new <#=code.Escape(entity)#>()
    {
<#+
    foreach(var edmProperty in primitiveProperties)
    {
#>
    <#=edmProperty.Name#> = this.<#=edmProperty.Name#>,
<#+
    }
#>
    };
}
<#+
}
#>

这并不完美,因为您需要记住在期望结果被序列化时返回 foo.ToSerializable() 而不是 foo 本身,但我希望它对某人有用。

【讨论】:

  • 感谢您的回复@daveharnett。然而,我最终做的是编写我自己的 JavaScriptSerializer 自定义版本。
【解决方案2】:

Travis,我知道您在这里有您接受的答案,但想就此进行一点横向思考。我最近遇到了一个非常相似的问题,无法为我工作,尝试了所有 [scriptignore] 属性等。

最终对我有用的是使用 Automapper 并创建从代理对象到精简的 poco 对象的映射。这在 2 分钟内解决了我所有的问题。所有这一切都是在试图让代理人打球时占了上风的 36 小时围攻心态之后 :-)

在此期间要考虑的另一种方法。

[编辑] - 使用 Automapper(这是一个引用 automapper 的小测试应用)

参考:http://automapper.codeplex.com/

nuget: 安装包 AutoMapper

类:

public sealed class One : BaseViewModel
{
    // init collection in ctor as not using EF in test
    // no requirement in real app
    public One()
    {
        Two = new Collection<Two>();
    }
    public int OneId { get; set; }
    public ICollection<Two> Two { get; set; }
}

public class Two
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
    [ScriptIgnore]
    public virtual One One { get; set; }
}

public abstract class BaseViewModel
{
    public string AsJson()
    {
        var serializer = new JavaScriptSerializer();
        return serializer.Serialize(this);
    }
}

public class OnePoco  : BaseViewModel
{
    public int OneId { get; set; }
    public virtual ICollection<TwoPoco> Two { get; set; }
}

public class TwoPoco
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
}

测试控制器代码:

public ActionResult Index()
{
    // pretend this is your base proxy object
    One oneProxy = new One { OneId = 1 };
    // add a few collection items
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 1, One = oneProxy});
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 2, One = oneProxy});

    // create a mapping (this should go in either global.asax 
    // or in an app_start class)
    AutoMapper.Mapper.CreateMap<One, OnePoco>();
    AutoMapper.Mapper.CreateMap<Two, TwoPoco>();

    // do the mapping- bingo, check out the asjson now
    // i.e. oneMapped.AsJson
    var oneMapped = AutoMapper.Mapper.Map<One, OnePoco>(oneProxy);

    return View(oneMapped);
}

试一试,看看你的进展如何,它确实对我有用,“地球”移动了 :)

【讨论】:

  • 你能稍微扩展一下 Automapper 吗?也许有一个简单的例子?另请注意,如已接受的答案中所述更改上下文的配置将导致正确处理 [ScriptIgnore] 标记。
  • 当然,automapper 是许多问题的“灵丹妙药”,我不能夸大这一事实。 automapper 允许您在复杂的无形对象和更简单的数据库/序列化友好对象之间创建映射。我将根据我从事的工作(通常是复杂的 viewModels-> poco)用一个例子来更新我的答案。给我30分钟左右。
  • 这给了我运行时异常“缺少类型映射配置或不支持的映射。映射类型: -> ”与 EF 5 和 AutoMapper 2.2。
  • 你好@R。施勒尔斯。不幸的是,当时在 EF4 和 automapper v2.0 上对此进行了测试。但是,我认为问题在于您无法映射到 DynamicProxy(至少,我怀疑)。开始基本的 1st 并映射到 pocos 和具体类型
  • 谢谢@jimtollan,我明白了。我主要添加了我的评论,以警告其他读者注意不是未来证明的解决方案。我对微软选择不让动态代理继承原始实体的事实感到困惑,但决定在这种情况下跳过 AutoMapper 并手动复制我需要的字段......
【解决方案3】:

这是一个已知问题

我们修复了 ScriptIgnoreAttribute 中的一个问题,该问题未传播到派生类。由于 POCO 代理类型是通过派生自用户提供的 POCO 类创建的,因此 JavaScriptSerializer 无法看到您在 repro 中拥有的 [ScriptIgnore] 属性。

该修复不会包含在 .NET 4.5 的下一个预览版中。

(所以大概你必须等待下一个预览版本或最终版本)

http://connect.microsoft.com/VisualStudio/feedback/details/723060/ef-4-2-code-first-property-attributes-not-honoured

在 .NET 4.5 中已修复

从有关该问题的 cmets 看来,如果您使用的是 JSON.Net 的当前版本,您可以使用 NonSerializedAttribute 而不是 ScriptIgnoreAttribute 来解决问题

【讨论】:

  • “虽然最终使用了 ScriptIgnore 或 JsonIgnore 属性,但这会严重削弱 Entity Framework 并迫使您进行比平时更多的管道工作 - 这样做违反了 EF 恕我直言的原则。 "从解决方法。我很欣赏这个链接,并看到了禁用此功能的建议,但这意味着在我对数据库运行的每个查询中明确包含字段,因此我只需要找到不同的方法。
  • 是的,但我认为解决方法是在微软进一步回应票证之前编写的。实际上,MSFT 提供的解决方案比解决方法中指出的要好。如果您将 ScriptIgnore 更改为 NonSerialized 并拥有最新版本的 JSON.Net,我相信这将适用于今天。
  • NonSerialized 不适用于虚拟、悲伤的脸。然而,巨大的笑脸,根据情况触发解决方法似乎确实有效。我在发送标志时这样做:context.Configuration.ProxyCreationEnabled = false; context.Configuration.LazyLoadingEnabled = false; 我将此标记为正确答案,因为它指向此解决方法。
猜你喜欢
  • 1970-01-01
  • 2011-12-31
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多