【问题标题】:JavaScriptSerializer circular reference when using ScriptIgnore使用 ScriptIgnore 时的 JavaScriptSerializer 循环引用
【发布时间】:2013-01-28 19:02:37
【问题描述】:

我将我的实体框架实体从我的 Web 项目和数据访问层拆分为一个单独的类库。在我的控制器中,我调用我的存储库以获取IEnumerable<RobotDog.Entities.Movie>,然后尝试使用JavaScriptSerializer 序列化为json,但即使我使用的是[ScriptIgnore] 属性,我也会得到循环引用。

重要提示:最初我在一个项目中拥有我的实体、数据访问和网络,并且我能够在没有循环引用的情况下成功序列化我的实体。当我创建单独的图层时,我开始遇到问题。我没有更改任何实体。

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();
        }
    }
}

【问题讨论】:

  • 大多数人在那时切换到 JSON.net。另一种方法是通过覆盖允许循环引用的部分来编写自己的 javascriptserializer 实现。
  • 是的,我查看了JsonConvert,但我不喜欢在它应该工作时必须编写更多代码的想法,并且最初在我分成多个项目之前工作,只是装饰一个属性与[ScriptIgnore]。我还研究了创建一个 viewModel,然后使用 AutoMapper 映射这两种类型。但我就是无法绕开必须编写更多代码来完成没有代码就应该完成的事情。另外,无法弄清楚为什么这不起作用让我发疯:)

标签: asp.net-mvc entity-framework circular-reference javascriptserializer


【解决方案1】:

也许响应有点晚,但我在 POCO Classes for Entity Framework Code-Firts 上遇到了类似的问题。问题是可能的属性被声明为虚拟的。在这种情况下,EF 创建了覆盖虚拟属性的代理类。似乎 ScriptIgnore 属性默认情况下不会应用于覆盖的属性,除非您像这样使用它:

[ScriptIgnore(ApplyToOverrides=true)]

【讨论】:

  • 是的,这正是我所需要的。谢谢!太糟糕了,这不是批准的答案。
  • 我应该在哪里写上面的代码行?我有同样的问题
  • 太棒了,这真的帮助了我,在过去的 6 个小时里让我摆脱了这个问题。非常感谢!!!
  • 谢谢!维护大量视图模型肯定有非零成本。有些地方这是正确的答案。不总是,但肯定有时。
  • 这绝对是要走的路。
【解决方案2】:

圆形对象图不能被 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 形式向客户端公开的信息。打破所有循环引用。随意拥有此主视图模型引用的其他视图模型,以便映射其他实体。

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

【讨论】:

  • 我实际上已经走上了这条路。我已经把 viewModel 写出来了,我打算使用 AutoMapper 将我的 viewModel 映射到我的域模型。我知道这会奏效。我不明白为什么[ScriptIgnore] 在我将我的项目分成多个层(即:Web、业务、DAL)之前工作,而现在它不会。除了具有循环依赖的属性之外,所有其他属性都应该序列化为 json - 所以我认为创建一个 viewModel 只是矫枉过正。
  • 您的意思是,最好将域模型与 UI 分开,即使它是一对一映射并且不需要排除/更改任何属性?
  • 是的,这正是我要说的。当然,如果它是一对一的映射,并且您的域模型中有循环引用,那么如果您使用视图模型,您将遇到同样的问题。在现实世界的应用程序中,它很少是一对一的映射。
  • 您是否建议创建我自己的映射器或使用像 Automapper 这样的第三方 dll,我想它使用反射来映射自动映射成员?
  • 我不建议您创建自己的映射器并重新发明轮子。 AutoMapper 使用反射,但它通过缓存昂贵的操作来智能地使用它。您绝对不必担心 AutoMapper 的性能。我在生产中的一些非常高流量的应用程序中使用它,与 MVC 管道执行的其他部分相比,映射开销可以忽略不计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多