使用ScriptIgnore时JavaScriptSerializer出现循环引用

13

我将我的Entity Framework实体从Web项目和数据访问层中拆分出来,放在单独的类库中。在我的控制器中,我调用我的存储库来获取一个IEnumerable<RobotDog.Entities.Movie>,然后尝试使用JavaScriptSerializer序列化为JSON,但是我收到了循环引用的错误,即使我使用了[ScriptIgnore]属性。

重要提示:最初,我的实体、数据访问和Web都在一个项目中,我成功地序列化了我的实体而没有循环引用。当我创建了单独的层时,问题就出现了。我没有更改任何实体。

一个位于RobotDog.Entities命名空间中的实体示例:

namespace RobotDog.Entities {
    public class Character {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [MaxLength(200)]
        public string Name { get; set; }

        public virtual Person Person { get; set; }

        [ScriptIgnore]
        public virtual Movie Movie { get; set; }
    }
}

我的控制器:

namespace RobotDog.Web.Controllers {
    public class MoviesController : Controller {
        private UnitOfWork _unitOfWork = new UnitOfWork();

        [HttpGet]
        public ActionResult Index() {
            var user = Membership.GetUser(User.Identity.Name);
            if(user != null) {
                var movies = _unitOfWork.UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey).Select(m => m.Movie);
                var serializer = new JavaScriptSerializer();
                var json = serializer.Serialize(movies);
                return View(json);
            }
            return View();
        }

    }
}

我的代码库:

namespace RobotDog.DataAccess.Movies {
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class {
        internal MovieContext Context;
        internal DbSet<TEntity> DbSet;

        public Repository(MovieContext context) {
            if (context == null)
                throw new ArgumentNullException("context");

            Context = context;
            DbSet = Context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null ) {
            IQueryable<TEntity> query = DbSet;

            if (predicate != null)
                query = query.Where(predicate);

            return orderBy != null ? orderBy(query).ToList() : query.ToList();
        }
    }
}

1
大多数人在这个时候会转向JSON.net。另一种方法是通过覆盖允许循环引用的部分来编写自己的javascriptserializer实现。 - Travis J
是的,我研究了JsonConvert,但我不喜欢在本应该工作的情况下需要编写更多代码的想法。最初,通过使用[ScriptIgnore]修饰属性,它可以在将其分成多个项目之前正常工作。我还研究了创建viewModel,然后使用AutoMapper将两种类型映射起来。但我无法理解为什么要编写更多代码才能完成不需要代码完成的任务。此外,无法弄清楚为什么这不起作用正在让我发疯 :) - bflemi3
2个回答

25

也许有点晚了,但我曾遇到与Entity Framework Code-First的POCO类相似的问题。 问题是我的许多属性被声明为virtual。 在这种情况下,EF会创建代理类来覆盖virtual属性。 看起来ScriptIgnore属性不会默认应用于重写的属性,除非你像这样使用:

[ScriptIgnore(ApplyToOverrides=true)]

1
是的,这正是我需要的。谢谢!不过很遗憾这不是被批准的答案。 - Adam Szabo
我应该在哪里写上面的代码?我有同样的问题。 - DharaPPatel
太棒了,这真的帮了我很多,让我摆脱了过去6个小时的问题。非常感谢!!! - stak
谢谢!维护大量视图模型确实存在一定的成本,有些情况下这是正确的答案。并非总是如此,但肯定有时候是这样的。 - Yablargo
这绝对是正确的方式。 - Wilmer SH

9
循环对象图无法进行JSON序列化。如果你仔细想一想,这其实是有道理的。正确的处理方法是使用视图模型。你永远不应该直接将领域实体传递给视图。始终定义一个只包含所需属性的视图模型。
我相信消费此JSON的客户端并不关心具有循环对象图。因此,简单地定义一个视图模型来打破这种循环依赖,并仅包括所需的属性。
然后,您需要将您的领域模型映射到视图模型,并将此视图模型传递给JsonResult(是的,这是您代码中的另一个问题 - 您正在手动进行JSON序列化并编写控制器操作中的管道代码,而不是将其委托给框架)。
所以:
[HttpGet]
public ActionResult Index() 
{
    var user = Membership.GetUser(User.Identity.Name);
    if(user != null) 
    {
        IEnumerable<Movie> movies = _unitOfWork
            .UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey)
            .Select(m => m.Movie);
        IEnumerable<MovieViewModel> moviesVm = ... map the domain model to your view model
        return Json(moviesVm, JsonRequestBehavior.AllowGet);
    }

    // return an empty movies array
    var empty = Enumerable.Empty<MovieViewModel>();
    return Json(empty, JsonRequestBehavior.AllowGet);
}

现在你应该把注意力放在定义MovieViewModel类上,该类只包含您想要作为JSON向客户端公开的信息。打破所有循环引用。可以随意使用其他视图模型,以映射其他实体。

最重要的是: 不要将领域模型传递给视图。始终定义视图模型。这样,您的应用程序完全独立于您正在使用的底层数据访问技术。您可以随意修改DAL层而不影响UI部分,因为此UI由视图模型表示。


我曾经走过这条路。我已经编写了viewModels,并打算使用AutoMapper将我的viewModels映射到我的领域模型。我知道这会起作用。我不明白的是为什么在我将项目拆分成多个层(即:Web,Business,DAL)之前,[ScriptIgnore]可以正常工作,现在却不能。除了具有循环依赖关系的属性之外,所有其他属性都应序列化为json-因此,我认为创建一个viewModel只是多余的。 - bflemi3
你的意思是,即使存在一对一映射且没有需要排除/更改的属性,最佳实践仍然是将领域模型与UI分开吗? - bflemi3
是的,完全正确,这正是我所说的。当然,如果它是一对一映射,并且您的领域模型中存在循环引用,那么如果您使用视图模型,您将遇到相同的问题。在现实世界的应用程序中,很少有一对一的映射。 - Darin Dimitrov
你会建议我创建自己的映射器还是使用第三方dll,比如Automapper,我想它使用反射来自动映射成员? - bflemi3
2
我不建议您创建自己的映射器并重新发明轮子。AutoMapper使用反射,但通过缓存昂贵的操作来智能地使用它。您绝对不应该担心AutoMapper的性能问题。我在一些非常高流量的生产应用程序中使用它,与MVC管道的其他部分相比,映射开销是可以忽略不计的。 - Darin Dimitrov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接