即使 ApplyToOverrides = true 也会忽略 ScriptIgnore 标签...它在 LinqPad 中有效

ScriptIgnore tag being ignored even with ApplyToOverrides = true... and it works in LinqPad

基本上,我正在尝试使用 JavaScriptSerializer 将一个简单的实体发送到 json。是的,我知道你想让我为此做一个多余的 class 并通过 AutoMapper 推送它,我是在自找麻烦。幽默一下。

I'm using Entity Framework6 to fetch a simple object 来获取一个简单的对象。

这是我的测试代码:

    [TestMethod]
    public void TestEntityTest()
    {
        var db = new TestDbContext();
        var ent = db.ResupplyForms.SingleOrDefault(e => e.Guid == new Guid("55117161-F3FA-4291-8E9B-A67F3B416097"));
        var json = new JavaScriptSerializer().Serialize(ent);
    }

非常简单。获取东西并序列化它。

错误如下:

An exception of type 'System.InvalidOperationException' occurred in System.Web.Extensions.dll but was not handled in user code

Additional information: A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.ResupplyForm_13763C1B587B4145B35C75CE2D5394EBED19F93943F42503204F91E0B9B4294D'.

这是实体:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Data.Entity.Spatial;
using Rome.Model.DataDictionary;
using System.Web.Script.Serialization;

namespace Rome.Model.Form
{
    [Table(nameof(ResupplyForm))]
    public partial class ResupplyForm
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ResupplyFormID {get;set;}

        public Guid Guid {get;set;}

        public int? RecordStatus {get;set;}

        [ForeignKey(nameof(RecordStatus))]
        [ScriptIgnore(ApplyToOverrides = true)]
        public virtual LookupItem RecordStatusLookupItem {get;set;} 
    }
}

我将省略 LookupItem 的 def,因为它进入了我项目的整个其余部分的模式,并且没有理智的世界应该重要,因为我已经将其标记为 "ignored"。

这是一个超级简单的上下文:

public class TestDbContext : DbContext
{
    public TestDbContext()
        : base("data source=.;initial catalog=studybaseline;integrated security=True;pooling=False;multipleactiveresultsets=False")
    {

    }

    public virtual DbSet<ResupplyForm> ResupplyForms { get; set; }
}

现在,致命一击:运行完美的 LinqPad 查询,使用与我的代码段完全相同的代码:

var db = new Rome.Model.Form.TestDbContext();
var ent = db.ResupplyForms.SingleOrDefault(e => e.Guid == new Guid("55117161-F3FA-4291-8E9B-A67F3B416097"));
var json = new JavaScriptSerializer().Serialize(ent).Dump();

哪个开心returns

{"ResupplyFormID":1,"Guid":"55117161-f3fa-4291-8e9b-a67f3b416097","RecordStatus":null}

我整天都在为这个问题绞尽脑汁,所以非常感谢您的帮助。

好的,我在一天后进一步研究并找到了原因:它与 [ScriptIgnore(ApplyToOverrides = true)] 事情没有任何关系。它与 Entity Framework 为每个实体创建的 EntityProxy subclass 有关。我的 ResupplyForm 在我的测试中实际上并没有用作 ResupplyForm...而是一个 EntityProxy subclass.

这个 EntityProxy subclass 添加了一个新成员,_entityWrapper。如果 EntityProxy 正在包装一个没有导航属性的 class,那么 _entityWrapper 不包含任何循环......但是一旦你添加一个导航 属性,_entityWrapper 就会包含循环,这会破坏序列化。

模糊的错误信息毁了一切。如果 JavaScriptSerializer 告诉我哪个字段有问题,我可以节省很多时间。

无论如何,我应该考虑切换到 NewtonSoft,但这会产生自己的问题(对于另一个 post),但我创建了一个 非常 粗略的解决方法:

public static class JsonSerializerExtensions
    {
        /// <summary>
        /// Convert entity to JSON without blowing up on cyclic reference.
        /// </summary>
        /// <param name="target">The object to serialize</param>
        /// <param name="entityTypes">Any entity-framework-related types that might be involved in this serialization.  If null, it will only use the type of "target".</param>
        /// <param name="ignoreNulls">Whether nulls should be serialized or not.</param>
        /// <returns>Json</returns>
        /// <remarks>This requires some explanation: all POCOs used by entites aren't their true form.  
        /// They're subclassed proxies of the object you *think* you're defining.  These add a new member
        /// _EntityWrapper, which contains cyclic references that break the javascript serializer.
        /// This is Json Serializer function that skips _EntityWrapper for any type in the entityTypes list.
        /// If you've a complicated result object that *contains* entities, forward-declare them with entityTypes.
        /// If you're just serializing one object, you can omit entityTypes.
        ///</remarks>
        public static string ToJsonString(this object target, IEnumerable<Type> entityTypes = null, bool ignoreNulls = true)
        {
            var javaScriptSerializer = new JavaScriptSerializer();
            if(entityTypes == null)
            {
                entityTypes = new[] { target.GetType() };
            }
            javaScriptSerializer.RegisterConverters(new[] { new EntityProxyConverter(entityTypes, ignoreNulls) });
            return javaScriptSerializer.Serialize(target);
        }
    }

    public class EntityProxyConverter : JavaScriptConverter
    {
        IEnumerable<Type> _EntityTypes = null;
        bool _IgnoreNulls;
        public EntityProxyConverter(IEnumerable<Type> entityTypes, bool ignoreNulls = true) : base()
        {
            _EntityTypes = entityTypes;
            _IgnoreNulls = ignoreNulls;
        }

        public override IEnumerable<Type> SupportedTypes
        {
            get
            {
                return _EntityTypes;
            }
        }

        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            var result = new Dictionary<string, object>();
            if (obj == null)
            {
                return result;
            }
            var properties = obj.GetType().GetProperties(
                System.Reflection.BindingFlags.Public 
                | System.Reflection.BindingFlags.Instance 
                | System.Reflection.BindingFlags.GetProperty
            );
            foreach (var propertyInfo in properties.Where(p => Attribute.GetCustomAttributes(p, typeof(ScriptIgnoreAttribute), true).Length == 0))
            {
                if (!propertyInfo.Name.StartsWith("_"))
                {
                    var value = propertyInfo.GetValue(obj, null);
                    if (value != null || !_IgnoreNulls)
                    {
                        result.Add(propertyInfo.Name, propertyInfo.GetValue(obj, null));
                    }
                }
            }
            return result;
        }
    }

您必须传入 classes(请记住,它们是即时生成的代理 classes)才能使用它,遗憾的是,它可能会惨败适用于任何合理的对象图,但它适用于简单的单个对象和数组等。它也无法与 JsonResult 一起使用,因为这些覆盖不能在那里使用。