将Entity Framework对象序列化为JSON

47

看起来使用WCF的原生DataContractJsonSerializer或ASP.NET的原生JavaScript序列化程序将Entity Framework对象序列化为JSON是不可能的。这是由于这两个序列化程序都拒绝引用计数问题所导致的。我还尝试过Json.NET,但它也会因为引用计数问题而失败。


编辑:Json.NET现在可以序列化和反序列化Entity Framework实体


我的对象是实体框架对象,它们被重载以执行额外的业务功能(例如身份验证等),我不想使用平台特定的属性对这些类进行装饰,因为我想呈现一个平台无关的API。
实际上,我已经在个人博客上记录了我所经历的每个步骤,网址为https://blog.programx.co.uk/2009/03/18/wcf-json-serialization-woes-and-a-solution/
我错过了什么明显的东西吗?

是的,我使用了JSon.NET序列化,但我想返回IQueryable<entity>而不是JSON字符串!如果我返回IQueryable<entity>,我可以利用OData。 - Davut Gürbüz
博客网站上的链接已经失效。 - Michael Freidgeim
@MichaelFreidgeim 没错,当有人删除另一篇文章时,我意识到了这个问题。那很不爽。看起来博客决定删除我的博客。我很不开心。我只能道歉。我花时间回顾了互联网档案,并在不同的位置重新发布。 - Program.X
8个回答

78

我的方法是将要序列化的数据投影到一个匿名类型中并进行序列化。这样可以确保只序列化我实际想要的JSON信息,而且不会无意中序列化对象图中更深层次的内容。代码如下:

var records = from entity in context.Entities
              select new 
              {
                  Prop1 = entity.Prop1,
                  Prop2 = entity.Prop2,
                  ChildProp = entity.Child.Prop
              }
return Json(records);

我发现匿名类型非常适合这种情况。显然,JSON不关心用哪种类型生成它。而使用匿名类型可以让你完全灵活地控制将哪些属性和结构放入JSON中。


1
优秀的解决方案。是否存在一种可行的方式将 JavaScript 对象反序列化为 EF(Entity Framework)对象? - Samuel Meacham
@Prabhu,我在帖子中的代码确实返回一个列表。你试过了吗? - Craig Stuntz
1
谢谢!我真的很厌倦为此将视图转换为DTO对象并且弄乱我的图表。 - MvcCmsJon
我相信在大多数情况下,我的回答比投影更好。我真正不想做的是:1.创建几乎相同的DTO对象;2.通过投影重新创建导航或自定义属性,就像这个答案所做的那样。如果你想避免这种情况,请查看我的答案:https://dev59.com/aVHTa4cB1Zd3GeqPSIAt - Tom Deloford
如果您有一对多的关系,可以在linq查询中使用lambda选择一组属性 - 例如:ListProps = entity.MyChildList.Select(c => new { c.Obj1.Prop1, c.Obj2.Prop1, c.Obj2.Prop2 } ).ToList() - Coruscate5
显示剩余2条评论

17

微软在将EF对象转换为数据契约时犯了错误,他们包含了基类和后向链接。

你最好的选择是为每个要返回的实体创建相应的数据传输对象类。这些类仅包括数据,不包括行为和EF特定部分的实体。您还需要创建方法来将DTO类与EF实体之间进行转换。

然后,你的服务将返回数据传输对象。


现在有一个选项可以使序列化单向进行。可能当您发布这篇文章时,该选项不存在。只是想提供这个信息,以防其他人在未来遇到此类问题。 - Yuck
13
@Yuck:请添加有关此功能的信息链接。 - John Saunders
据我所知,EF没有这样的设置。这只适用于Linq-to-SQL。 - Ziad

4
根据@Craig Stuntz的回答并类似于DTO,为了我的解决方案,我创建了模型的部分类(在单独的文件中)和一个返回对象方法,只使用将要用到的属性以及我希望的方式。
namespace TestApplication.Models
{
    public partial class Employee
    {
        public object ToObject()
        {
            return new
            {
                 EmployeeID = EmployeeID,
                 Name = Name,
                 Username = Username,
                 Office = Office,
                 PhoneNumber = PhoneNumber,
                 EmailAddress = EmailAddress,
                 Title = Title,
                 Department = Department,
                 Manager = Manager
            };
        }
    }
}

然后我在返回中简单调用它:

var employee = dbCtx.Employees.Where(x => x.Name == usersName).Single();
return employee.ToObject();

我认为被接受的答案更快捷易行,但我使用自己的方法来保持所有返回值一致且DRY。


2

我的解决方案是简单地删除子实体上的父引用。

因此,在我的模型中,我选择了关系并将父引用更改为内部而不是公共。

对于所有情况可能不是理想的解决方案,但对我有效。


1
我通过仅从 System 命名空间获取对象类型,将其转换为字典然后添加到列表中来解决了这个问题。对我来说很有效 :)
看起来很复杂,但这是唯一对我有效的通用解决方案...我正在使用这个逻辑来制作一个助手,所以它是为了特殊用途而设计的,在实体对象中需要拦截每个对象类型,也许有人可以根据自己的需求进行调整。
List<Dictionary<string, string>> outputData = new List<Dictionary<string, string>>();

// convert all items to objects
var data = Data.ToArray().Cast<object>().ToArray();

// get info about objects; and get only those we need
// this will remove circular references and other stuff we don't need
PropertyInfo[] objInfos = data[0].GetType().GetProperties();
foreach (PropertyInfo info in objInfos) {
    switch (info.PropertyType.Namespace)
    { 
          // all types that are in "System" namespace should be OK
          case "System":
              propeties.Add(info.Name);
              break;
     }
}
Dictionary<string, string> rowsData = null;
foreach (object obj in data) {
     rowsData = new Dictionary<string, string>();
     Type objType = obj.GetType();
     foreach (string propertyName in propeties)
     {
//if You don't need to intercept every object type You could just call .ToString(), and remove other code
         PropertyInfo info = objType.GetProperty(propertyName);
         switch(info.PropertyType.FullName)
         {
               case "System.String":
                    var colData = info.GetValue(obj, null);
                    rowsData.Add(propertyName, colData != null ? colData.ToString() : String.Empty);
                    break;
//here You can add more variable types if you need so (like int and so on...)
           }
      }

      outputData .Add(rowsData); // add a new row
}

"

outputData" 是安全的进行 JSON 编码...希望有人会发现这个解决方案有用。写这段代码很有趣 :)

"

1
我为这个问题苦战了好几天,
解决方案:在你的edmx窗口内。 - 右键单击并添加代码生成项 - 选择代码选项卡 - 选择EF 4x.POCOC实体生成器
如果你没有看到它,那么你将不得不使用nuget安装它,搜索EF。
实体生成器将把所有复杂类型和实体对象生成为简单类,以便序列化为json。

1

1
我同意Mehal的观点。我在这里的回答中扩展了你的例子以处理其他一些情况。https://dev59.com/aVHTa4cB1Zd3GeqPSIAt - Tom Deloford
链接已损坏。 - Michael Freidgeim
哦,我会尝试用一个新的来修复它。 - Mehal

1

我找到了一种替代方案

您可以将父关系设置为私有,这样在转换过程中就不会暴露属性,从而消除无限属性循环。


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