什么会使Entity Framework / Upshot相信我的对象图“包含循环”? [英] What would make Entity Framework / Upshot believe my object graph "contains cycles"?

查看:140
本文介绍了什么会使Entity Framework / Upshot相信我的对象图“包含循环”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Entity Framework 4.3(Code-First)测试Knockout 2.1.0和Upshot 1.0.0.2,并且遇到以下错误:


{类型
的对象图System.Collections.Generic.HashSet`1 [[KnockoutTest.Models.Person,
KnockoutTest,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null]]'
包含循环,如果参考跟踪是
禁用,则不能序列化。}


I我正在使用一个相当典型的模型来测试一些基本的父子实体。

  public class Client 
{
public Client()
{
Projects = new HashSet< Project>();
Persons = new HashSet< Person>();
}

[Key]
public int ClientId {get;组;

[必需]
[Display(Name =Client Name,Description =Client's name)]
[StringLength(30)]
public string名称{get;组; }

public ICollection< Project>项目{get;组; }
public ICollection< Person>人{get;组; }

}

public class Project
{
public Project()
{

}

[Key]
public int ProjectId {get;组; }

[StringLength(40)]
public string Name {get;组; }


public int? ClientId {get;组; }
public virtual Client Client {get;组;


public class Person
{
public Person()
{
PhoneNumbers = new HashSet< PhoneNumber>();
}

[Key]
public int PersonId {get;组;

[必需]
[Display(Name =First Name,Description =Person's first name)]
[StringLength(15)]
public string FirstName {get;组; }

[必需]
[Display(Name =First Name,Description =Person's last name)]
[StringLength(15)]
public string LastName {get;组; }

[ForeignKey(HomeAddress)]
public int? HomeAddressId {get;组; }
public Address HomeAddress {get;组; }

[ForeignKey(OfficeAddress)]
public int? OfficeAddressId {get;组; }
public Address OfficeAddress {get;组; }

public ICollection< PhoneNumber> PhoneNumbers {get;组; }

public int? ClientId {get;组; }
public virtual Client Client {get;组;
}

public class Address
{
[Key]
public int AddressId {get;组;

[必需]
[StringLength(60)]
public string StreetAddress {get;组;

[必需]
[DefaultValue(Laurel)]
[StringLength(20)]
public string City {get;组;

[必需]
[DefaultValue(MS)]
[StringLength(2)]
public string State {get;组;

[必需]
[StringLength(10)]
public string ZipCode {get;组; }
}

public class PhoneNumber
{
public PhoneNumber()
{

}

[Key]
public int PhoneNumberId {get;组; }

[必需]
[显示(名称=电话号码,描述=个人电话号码)]
public string Number {get;组; }

[必需]
[显示(名称=电话类型,描述=电话类型)]
[DefaultValue(Office)]
public string PhoneType {get;组; }

public int? PersonId {get;组; }
public virtual Person Person {get;组;
}

我的上下文是非常通用的。

  public class KnockoutContext:DbContext 

{
public DbSet< Client>客户{get;组; }
public DbSet< Project>项目{get;组; }
public DbSet< Person>人{get;组; }
public DbSet< Address>地址{get;组; }
public DbSet< PhoneNumber> PhoneNumbers {get;组;

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}

我也有一点样本数据 - 虽然不一定相关。

  protected override void Seed(KnockoutContext context)
{
base.Seed(context);

context.Clients.Add(new Client
{
Name =Muffed Up Manufacturing,
Persons = new List< Person> {
new Person {FirstName =Jack,LastName =Johnson,
PhoneNumbers = new List< PhoneNumber>
{
new PhoneNumber {Number =702-481-0283,PhoneType =办公室},
new PhoneNumber {Number =605-513-0381,PhoneType =Home}
}
},
new Person {FirstName =Mary ,LastName =Maples,
PhoneNumbers = new List< PhoneNumber>
{
new PhoneNumber {Number =319-208-8181,PhoneType =Office},
new PhoneNumber {Number =357-550-9888,PhoneType =Home }
}
},
new Person {FirstName =Danny,LastName =Doodley,
PhoneNumbers = new List< PhoneNumber>
{
new PhoneNumber {Number =637-090-5556,PhoneType =Office},
new PhoneNumber {Number =218-876-7656,PhoneType =Home }
}
}
},
Projects = new List< Project>
{
new Project {Name =Muffed up Assessment Project},
new Project {Name =New Product Design},
new Project {Name =Razor Thin Margin},
new Project {Name =Menial Managerial Support}
}

}
);

context.Clients.Add(new Client
{
Name =Dings and Scrapes Carwash,
Persons = new List< Person> {new Person {FirstName =Fred,LastName =Friday},
new Person {FirstName =Larry,LastName =Lipstick},
new Person {FirstName =Kira,LastName =Kwikwit }
},
Projects = new List< Project>
{
new Project {Name =Wild and Crazy Wax Job},
new Project {Name = Pimp Ride Detailing},
new Project {Name =Saturday Night Special},
new Project {Name =Soapy Suds Extra}
}
}
);


IEnumerable< DbEntityValidationResult> p = context.GetValidationErrors();

if(p!= null)
{
foreach(p中的DbEntityValidationResult项)
{
Console.WriteLine(item.ValidationErrors);
}
}
}

}

基本上,每当我尝试使用客户,人员,项目等的包含时,都会发现类似的错误。

 命名空间KnockoutTest.Controllers 
{

public class ClientController:DbDataController< KnockoutTest.Models.KnockoutContext>
{
public IQueryable< Client> GetClients()
{
return DbContext.Clients.Include(Persons)。OrderBy(o => o.Name);
}
}


public class ProjectController:DbDataController< KnockoutTest.Models.KnockoutContext>
{
public IQueryable< Project> GetProjects()
{
return DbContext.Projects.OrderBy(o => o.Name);
}
}


public class PersonController:DbDataController< KnockoutTest.Models.KnockoutContext>
{
public IQueryable< Person> GetPersons()
{
return DbContext.Persons.Include(Client)。OrderBy(o => o.LastName);
}
}

public class AddressController:DbDataController< KnockoutTest.Models.KnockoutContext>
{
public IQueryable< Address> GetAddresses()
{
return DbContext.Addresses.OrderBy(o => o.ZipCode);
}
}

public class PhoneNumberController:DbDataController< KnockoutTest.Models.KnockoutContext>
{
public IQueryable< PhoneNumber> GetPhoneNumbers()
{
return DbContext.PhoneNumbers.OrderBy(o => o.Number);
}
}
}

你能看到任何理由.NET应该抱怨这个模式?



无论如何,我有什么选择可以解决?



解决方案

简单的回答是Steve Sanderson的Knockout,Upshot和Entity Framework 4的演示。 x Code-First构建单页应用程序(虽然很棒!!!)可能有点误导。这些工具并没有像乍看起来一样出色。 [Spoiler:我相信有一个合理的解决方法,但它涉及到微软竞争对手以外的微软。]



(对于史蒂夫的梦幻单页应用程序SPA)演示,请访问 http://channel9.msdn.com / Events / TechDays / Techdays-2012-the-Netherlands / 2159 ,值得一看。)



在大多数任何Web应用程序中,我们在概念上需要以下列方式移动和操作数据:


数据源(通常是数据库) - > Web应用程序 - >浏览器客户端


AND


浏览器客户端 - > Web应用程序 - >数据源(通常是数据库)


过去,操作数据接收数据并将其传输到数据库是真正的噩梦如果你必须在.NET 1.0 / 1.1天之内,你可能会记得一个开发过程,其中包括以下步骤:




  • 手动定义数据模型

  • 创建所有表,建立关系,手动定义索引和约束等。

  • 创建和测试存储过程以访问数据 - 通常手动指定每个过程中要包括的每个字段。

  • 创建POCO(普通旧CLR对象)以保存数据

  • 代码打开与数据库的连接,并迭代地递归返回每个记录,并将其映射到POCO对象。



这只是为了获得数据进入系统。反过来,我们不得不以相反的顺序重复这些步骤。关键是数据库编码非常耗时(真的很无聊)。显然,一些代码生成和其他工具来了,简化了事情。



真正的突破是使用NHibernate,Entity Framework 4(Code-First方法)和其他类似的(几乎)从开发人员完全抽取数据库的ORM工具。这些工具不仅提高了开发速度,而且提高了整体代码质量,因为它们错误地引入错误的机会较少。



现在,在许多应用程序中,连接到和与数据库是(几乎)一个事后的想法,因为大多数数据库的细节被隐藏。



微软还提供了Upshot.js和WebAPI的想法,这两个工具,当与服务器和数据库之间的NHibernate和Entity Framework 4所做的相同的方式,将彻底改变服务器和浏览器之间的通信。



这确实是一个非常有价值的成就 - 特别是因为客户正在推动更多的交互式Web应用程序。



阻止开发人员移动的主要障碍之一更多的(浏览器)客户端的用户界面是需要大量的编码。一些步骤包括:




  • 将数据传输给客户端(通常为JSON格式)

  • 将.NET对象中的所有属性映射到JavaScript对象

  • 重新创建关于对象及其属性的所有元数据。

  • 将数据绑定到客户端浏览器中的元素

  • 监视更改

  • 一旦重新映射数据(发送回服务器)已被修改

  • 将数据发回服务器



这真的好像是 deja vu,因为它的复杂性与将数据进出数据库的遗留过程非常相似。



根据Web应用程序的配置方式,可能会一旦将数据返回到服务器到实际的数据库对象,就需要额外的有趣的映射。 (这将是更多的情况。)



这个服务器 - >客户端 - >服务器数据传输需要大量的编码,并提供了许多意想不到的挑战的机会Don不要忘了调试JavaScript多么有趣! (好的,现在比几年前还要痛苦,但是在Visual Studio中调试C#代码还不如开发人员友好。)



Steve桑德森关于单页应用程序的演示提供了一个截然不同(更好)的解决方案。



这个想法是,WebAPI,Upshot.js和Knockout将能够无缝地将数据传递给浏览器客户端并从浏览器客户端接收数据,同时提供高度交互式的用户体验。哇!这不是让你只想伸出援手吗?



虽然这个想法不是新的,但它是我认为第一个认真的努力之一一旦数据通过WebAPI传递并到达客户端(通过Upshot),那么像Knockout这样的框架就可以使用数据。并提供非常高水平的互动性前沿Web应用程序所需。 (虽然可能不会立即清楚,但我所描述的是通过加载页面而不是主要通过AJAX请求传达JSON格式数据的主要功能的应用程序。)



任何削减所有这些编码的工具显然将被开发人员社区迅速拥抱。



Upshot.js(RIA的重命名和升级版本/ JS)应该照顾上面列出的几个平凡任务。它应该是WebAPI和Knockout之间的粘合剂。它旨在动态地映射从.NET中转义为JSON或XML的对象,并将对象属性,必需字段,字段长度,显示名称,描述等等的相关元数据公开(元数据为什么允许映射和可以被访问以用于验证。)



注意:我仍然不确定如何访问结果元数据并将其绑定到一个验证框架,如jQuery验证或一个Knockout验证插件。这是在我的待办事项列表中进行测试。



注意:我不确定哪些类型的元数据支持哪些。这是在我的待办事项列表来测试。作为附注,我还计划在System.ComponentModel.DataAnnotations之外的元数据进行实验,以查看是否支持NHibernate属性以及自定义属性。


$ b $所以考虑到所有这一切,我开始使用Steve在他的演示中在一个真实的Web应用程序中使用的相同技术。这些包括:




  • 实体框架4.3使用Code-First方法

  • ASP.NET MVC4与WebAPI

  • Upshot.js

  • Knockout.js



预期所有这些技术将一起运作良好,因为a)他们是最新的微软工具(除了开源的Knockout),而且由于现在的微软的史蒂夫·桑德森(Steve Sanderson)在微软的主要演示文稿中一起使用它们,开发单页应用程序。



不幸的是,我在实践中发现,实体框架4.x和Upshot.js以非常不同的方式查看世界,其方向是如前所述,实体框架代码首先做了一个非常好的工作,允许开发人员定义高度功能的对象模型,它将近似魔术地转化为功能性的数据库。



其中之一实体框架4.x代码第一的伟大功能是从父对象导航到子级的能力,并从子对象返回到其父级。这些双向协会是EF的基石。他们节省了大量的时间,大大简化了开发。此外,微软已经反复声称这个功能是使用实体框架的重要原因。



在Scott Guthrie的博客文章( http://weblogs.asp.net/scottgu/archive/2010他最初介绍并介绍了EF 4 Code First的方法,他演示了以下两个方面的双向导航的概念:课程:

  public class Dinner 
{
public int DinnerID {get;组; }
public string标题{get;组; }
public DateTime EventDate {get;组; }
public string Address {get;组; }
public string HostedBy {get;组; }

public virtual ICollection< RSVP> RSVP {get;组; }
}

public class RSVP
{
public int RsvpID {get;组; }
public int DinnerID {get;组; }
public string AttendeeEmail {get;组; }
公开虚拟晚餐晚餐{get;组;
}

正如你所看到的,Dinner包含与RSVP的关联,RSVP包含晚餐协会在互联网上有许多其他例子在许多变化中发生。



由于这两种方式的关联是实体框架的核心特征,所以合理的人可能会期望Microsoft将支持该库中使用的功能(Upshot.js),它将数据从.NET服务器应用程序提供给客户端。如果功能不受支持,那么这可能是他们想要共享的东西,因为它将显着关键的体系结构决策,并且最不喜欢使用任何正确设计的EF 4 Code First实现。



在我的测试代码(在上面的原始问题中列出),我自然认为支持正常的EF Code-First功能(如双向绑定/导航),因为这是演示文稿显示的内容。 >

但是,我立即开始收到令人讨厌的小运行时错误,如:


对象图形
'System.Collections.Generic.HashSet`1 [[KnockoutTest.Models.Person,
KnockoutTest,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null]]'
包含循环,如果参考跟踪是
禁用,则不能序列化。


我尝试了许多不同的方法来尝试和修复问题。根据我的想法和我的阅读,这里是我尝试的一些失败的解决方案。




  • 从关系的一边删除关联。这不是一个很好的解决方案,因为在父母和孩子之间可以在每个方向导航是非常方便的。 (这可能是为什么这些关联属性被称为导航属性。)删除任何一方的关系都有副作用。当父母的关系被删除时,浏览列表的孩子的能力也被删除。当这个关系从孩子中删除时,.NET为我提供了另一个友好的错误。




无法检索关联信息关联
'KnockoutTest.Models.Client_Persons'只支持包含外部
密钥信息的模型,请参阅实体框架文档,了解
创建包含外键信息的模型的详细信息。





  • 为了防止系统变得混乱,作为外键,我在子实体上明确指定了一个[ForeignKey]属性。所有的编译,但.NET返回对象图类型...包含循环,不能序列化...


  • 我的一些阅读表明添加WCF中的[DataContract(IsReference = true)]这样的属性可能会让.NET对于循环引用感到困惑。这是当我得到这个美丽。





类型'KnockoutTest.Models.Person '不能被序列化到JSON
,因为它的IsReference设置是'True'。JSON格式不支持
支持引用,因为没有标准格式的
表示引用为了启用序列化,禁用
IsReference设置在
类型的类型或适当的父类上。


此错误非常重要的是因为它基本上告诉我们,我们不能够在其正常配置中一起使用Upshot和Entity Framework Code。为什么?实体框架旨在利用双向绑定。但是,当双向绑定实现时,Upshot说它不能处理循环引用。当循环引用被管理时,Upshot基本上表示它不能处理父对象和对象之间的引用,因为JSON不支持它。



当我观看Steve的演示时,我回想起他DID在客户和交货之间有关系。我决定回去仔细观察他的对象模型。

  public class Customer 
{
public int CustomerId {get;组; }
public string Name {get;组; }
public string Address {get;组;


public class Delivery
{
//主键和与客户
的一对多关系public int DeliveryId {get;组; }
public virtual int CustomerId {get;组; }
public virtual Customer Customer {get;组; }

//此传递的属性
public string说明{get;组; }
public bool IsDelivered {get;组; } //< - 这是我们主要感兴趣的

我们发现是在Steve的演示中,他的关系只有一种方式,它将孩子绑定到父母而不是孩子的父母。



在这个演示中,作品。然而,在许多现实世界的应用中,这种方法使数据访问变得不切实际。例如,我在原来的问题中包含的演示场景。我们有:

 客户
项目
人员
地址
PhoneNumbers

大多数开发者,我不想从地址或电话号码开始查询。他们希望能够选择客户或项目或人员的列表,然后导航到其后代列表。



我不是100%肯定这是不可能的使用启用双向绑定的实体,但我不知道任何可能仅使用Microsoft工具可能会获得成功的配置。



幸运的是,我认为有一个解决方案(它负责处理循环依赖问题),我计划在接下来的几天内进行测试。该解决方案是... JSON.Net



JSON.Net支持循环依赖关系并维护对子对象的引用。如果它按预期工作,它将照顾我在测试中遇到的两个错误。



一旦我测试了,我会在这里报告结果。



我认为史蒂夫的演示是辉煌的,我喜欢他的演示。我相信Knockout是惊人的。我非常感谢微软似乎要去哪里。如果这个工具有一些值得注意的局限性,那么我认为微软也许应该会比较接近他们。



我并不是太过于批评微软我根本不批评史蒂夫),因为我认为他们做得很好。我非常喜欢看到有人采取结果并重新考虑它(和WebAPI),所以我非常喜欢它可以与Entity Framework完全集成而不使用第三方工具。



我不知道NHiberbnate是否有类似的工具,但我会喜欢甚至可能看到有人延续了与NHibernate集成(或开发类似的库)的结果。通过扩展,我主要在谈论从NHibernate消费元数据。



当我测试JSON.Net时,我也打算测试NHibernate。


I am testing Knockout 2.1.0 and Upshot 1.0.0.2 with Entity Framework 4.3 (Code-First) and am running into the following error:

{"Object graph for type 'System.Collections.Generic.HashSet`1[[KnockoutTest.Models.Person, KnockoutTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled."}

I am using a fairly typical model for testing with some basic parent-child entities.

public class Client
{
    public Client()
    {
        Projects = new HashSet<Project>();
        Persons = new HashSet<Person>();
    }

    [Key]
    public int ClientId { get; set; }

    [Required]
    [Display(Name = "Client Name", Description = "Client's name")]
    [StringLength(30)]
    public string Name { get; set; }

    public ICollection<Project> Projects { get; set; }
    public ICollection<Person> Persons { get; set; }

}

public class Project
{
    public Project()
    {

    }

    [Key]
    public int ProjectId { get; set; }

    [StringLength(40)]
    public string Name { get; set; }


    public int? ClientId { get; set; }
    public virtual Client Client { get; set; }
}

public class Person
{
    public Person()
    {
        PhoneNumbers=new HashSet<PhoneNumber>();    
    }

    [Key]
    public int PersonId { get; set; }

    [Required]
    [Display(Name="First Name", Description = "Person's first name")]
    [StringLength(15)]
    public string FirstName { get; set; }

    [Required]
    [Display(Name = "First Name", Description = "Person's last name")]
    [StringLength(15)]
    public string LastName { get; set; }

    [ForeignKey("HomeAddress")]
    public int? HomeAddressId { get; set; }
    public Address HomeAddress { get; set; }

    [ForeignKey("OfficeAddress")]
    public int? OfficeAddressId { get; set; }
    public Address OfficeAddress { get; set; }

    public ICollection<PhoneNumber> PhoneNumbers { get; set; }

    public int? ClientId { get; set; }
    public virtual Client Client { get; set; }
}

public class Address
{
    [Key]
    public int AddressId { get; set; }

    [Required]
    [StringLength(60)]
    public string StreetAddress { get; set; }

    [Required]
    [DefaultValue("Laurel")]
    [StringLength(20)]
    public string City { get; set; }

    [Required]
    [DefaultValue("MS")]
    [StringLength(2)]
    public string State { get; set; }

    [Required]
    [StringLength(10)]
    public string ZipCode { get; set; }
}

public class PhoneNumber
{
    public PhoneNumber()
    {

    }

    [Key]
    public int PhoneNumberId { get; set; }

    [Required]
    [Display(Name = "Phone Number", Description = "Person's phone number")]
    public string Number { get; set; }

    [Required]
    [Display(Name = "Phone Type", Description = "Type of phone")]
    [DefaultValue("Office")]
    public string PhoneType { get; set; }

    public int? PersonId { get; set; }
    public virtual Person Person { get; set; }
}

My context is very generic.

public class KnockoutContext : DbContext

{
    public DbSet<Client> Clients { get; set; }
    public DbSet<Project> Projects { get; set; }
    public DbSet<Person> Persons { get; set; }
    public DbSet<Address> Addresses { get; set; }
    public DbSet<PhoneNumber> PhoneNumbers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }
}

I also have a little bit of sample data--though it should not be relevant.

 protected override void Seed(KnockoutContext context)
        {
            base.Seed(context);

            context.Clients.Add(new Client
                                    {
                                        Name = "Muffed Up Manufacturing",
                                        Persons = new List<Person> { 
                                            new Person {FirstName = "Jack", LastName = "Johnson",
                                                PhoneNumbers = new List<PhoneNumber>
                                                    {
                                                        new PhoneNumber {Number="702-481-0283", PhoneType = "Office"}, 
                                                        new PhoneNumber {Number = "605-513-0381", PhoneType = "Home"}
                                                    }
                                            },
                                            new Person { FirstName = "Mary", LastName = "Maples", 
                                                PhoneNumbers = new List<PhoneNumber>
                                                    {
                                                        new PhoneNumber {Number="319-208-8181", PhoneType = "Office"}, 
                                                        new PhoneNumber {Number = "357-550-9888", PhoneType = "Home"}
                                                    }
                                            },
                                            new Person { FirstName = "Danny", LastName = "Doodley", 
                                                PhoneNumbers = new List<PhoneNumber>
                                                    {
                                                        new PhoneNumber {Number="637-090-5556", PhoneType = "Office"}, 
                                                        new PhoneNumber {Number = "218-876-7656", PhoneType = "Home"}
                                                    }
                                            }
                                        },
                                        Projects = new List<Project>
                                                       {
                                                           new Project {Name ="Muffed Up Assessment Project"},
                                                           new Project {Name ="New Product Design"},
                                                           new Project {Name ="Razor Thin Margins"},
                                                           new Project {Name ="Menial Managerial Support"}
                                                       }

                                    }
                );

            context.Clients.Add(new Client
                                    {
                                        Name = "Dings and Scrapes Carwash",
                                        Persons = new List<Person> { new Person {FirstName = "Fred", LastName = "Friday"},
                                            new Person { FirstName = "Larry", LastName = "Lipstick" },
                                            new Person { FirstName = "Kira", LastName = "Kwikwit" }
                                        },
                                        Projects = new List<Project>
                                                       {
                                                           new Project {Name ="Wild and Crazy Wax Job"},
                                                           new Project {Name ="Pimp Ride Detailing"},
                                                           new Project {Name ="Saturday Night Special"},
                                                           new Project {Name ="Soapy Suds Extra"}
                                                       }
                                    }
                );


            IEnumerable<DbEntityValidationResult> p = context.GetValidationErrors();

            if (p != null)
            {
                foreach (DbEntityValidationResult item in p)
                {
                    Console.WriteLine(item.ValidationErrors);
                }
            }
        }

    }

Basically, whenever I attempt to use an "Include" from Client, Person, Project, etc. I get a similar error to the one listed above.

namespace KnockoutTest.Controllers
{

    public class ClientController : DbDataController<KnockoutTest.Models.KnockoutContext>
    {
        public IQueryable<Client> GetClients()
        {
            return DbContext.Clients.Include("Persons").OrderBy(o => o.Name);
        }
    }


    public class ProjectController : DbDataController<KnockoutTest.Models.KnockoutContext>
    {
        public IQueryable<Project> GetProjects()
        {
            return DbContext.Projects.OrderBy(o => o.Name);
        }
    }


    public class PersonController : DbDataController<KnockoutTest.Models.KnockoutContext>
    {
        public IQueryable<Person> GetPersons()
        {
            return DbContext.Persons.Include("Client").OrderBy(o => o.LastName);
        }
    }

    public class AddressController : DbDataController<KnockoutTest.Models.KnockoutContext>
    {
        public IQueryable<Address> GetAddresses()
        {
            return DbContext.Addresses.OrderBy(o => o.ZipCode);
        }
    }

    public class PhoneNumberController : DbDataController<KnockoutTest.Models.KnockoutContext>
    {
        public IQueryable<PhoneNumber> GetPhoneNumbers()
        {
            return DbContext.PhoneNumbers.OrderBy(o => o.Number);
        }
    }
}

Can you see any reason why .NET should be complaining about this model?

Regardless, what options do I have to work around it?

Thank you for any assistance!

解决方案

The short answer is that Steve Sanderson's demonstration of Knockout, Upshot, and Entity Framework 4.x Code-First to build a Single Page Application was (while great!!!) maybe a little misleading. These tools do not play nearly as nicely together as they appear at first glance. [Spoiler: I do believe there is a reasonable workaround but it involves stepping outside of the Microsoft arena ever-so-slightly.]

(For Steve's fantastic Single Page Application (SPA) presentation, please visit http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2159. It is well worth a watch.)

In most any Web application, We conceptually need to move and manipulate data in the following way:

Data Source (often a Database) -> Web Application -> Browser Client

AND

Browser Client -> Web Application -> Data Source (often a Database)

In the past, manipulating data to receive it from and transmit it to the database was a real nightmare. If you have to be around in the .NET 1.0/1.1 days, you may recall a development process that included steps like:

  • Manually defining a data model
  • Creating all of the tables, setting up relationships, manually defining indexes and constraints, etc.
  • Creating and testing stored procedures to access the data - generally manually specifying each field to be included in each procedure.
  • Create POCO (Plain Old CLR Objects) to hold the data
  • Code to open a connection to the database and iteratively recurse each record returned and map it into the POCO objects.

This was just to get the data into the system. Going back the other way we had to repeat several of these steps in reverse order. The point is that database coding was very time consuming (and really quite boring). Obviously, a number code generation and other tools came along and simplified things.

The real breakthroughs were with NHibernate, Entity Framework 4 (Code-First approach), and other similar ORM tools which (almost) completely abstracted the database from the developer. These tools not only increased development speed, but also improved overall code quality since their were fewer opportunities to mistakenly introduce bugs.

Now, in many applications, connectivity to and interaction with a database is (almost) an afterthought since most database details are hidden away.

Microsoft has also provided Upshot.js and the WebAPI with the idea that these two tools, when used in conjunction with one another, are going to revolutionize the communication between the server and the browser in the same way that NHibernate and Entity Framework 4 have done between the server and the database.

This would indeed be a very worthy accomplishment--especially as clients are pushing for more interactive Web applications.

One of the main stumbling blocks that prevents developers from moving more of the user interface to the (browser) client is the significant amount of coding required. Some of the steps include:

  • Transmit the data to the client (usually in JSON format)
  • Map all of the properties from the .NET objects into JavaScript objects
  • Re-create all of the meta-data about the object and its properties
  • Bind that data to the elements in the client browser
  • Monitor changes
  • Re-map the data (for sending back to the server) once it has been modified
  • Transmit the data back to the server

This really seems quite like "deja vu" because it is quite similar in complexity to the legacy processes for getting data into and out of a database.

Depending on how the Web application is configured, there may be additional fun mapping the data once it returns to the server to actual database objects. (This will be the case more often than not.)

This server->client->server data transmission requires a lot of coding and offers many opportunities for unexpected challenges Don't forget how much fun it is to debug JavaScript! (Ok, it's less painful now than it was a couple of years ago, but it is still not as developer-friendly as debugging C# code in Visual Studio.)

Steve Sanderson's presentation on Single Page Applications offers a far different (and better) solution.

The idea is that WebAPI, Upshot.js, and Knockout would be able to seamlessly deliver data to and receive data from a browser client while providing a highly interactive user experience. Wow! Doesn't that make you just want to reach out and hug someone?

While this idea is not new, it is one of the first serious efforts I think have seen to really do this in .NET.

Once the data is delivered through the WebAPI and reaches the client (via Upshot), then frameworks like Knockout would be able to consume the data and deliver the very high level of interactivity cutting edge Web applications require. (While it may not be immediately clear, what I describing are applications which do not primarily function by loading "pages" but rather by primarily communicating JSON formatted data through AJAX requests.)

Any tool that cuts down on all of this coding is obviously going to be quickly embraced by the developer community.

Upshot.js (the renamed and upgraded version of RIA/JS) was supposed to take care of several of the mundane tasks listed above. It is supposed to be the glue between the WebAPI and Knockout. It is intended to dynamically map objects which are tansmitted in JSON or XML from .NET and also expose the associated meta-data for things like object properties, required fields, field lengths, display names, descriptions, etc. (The meta-data is what allows the mapping and MAY be accessed for use in validation.)

Note: I am still uncertain how to access the upshot meta-data and tie it to a validation framework like jQuery validation or one of the Knockout validation plugins. This is on my todo list to test.

Note: I am uncertain which of these types of meta-data are supported. This is on my todo list to test. As a side note, I also plan to experiment with meta-data outside of System.ComponentModel.DataAnnotations to also see if NHibernate attributes are supported as well as custom attributes.

So with all of this in mind, I set out to use the same set of technologies that Steve used in his demo in a real-world Web application. These included:

  • Entity Framework 4.3 using Code-First approach
  • ASP.NET MVC4 with WebAPI
  • Upshot.js
  • Knockout.js

The expectation is that all of these technologies would function well together because a) they are the latest Microsoft tools (with the exception of the open source Knockout) and because Steve Sanderson, currently of Microsoft, used them together in major Microsoft presentation demonstrating the development of single page application.

Unfortunately, what I found in practice was that Entity Framework 4.x and Upshot.js view the world in very different ways and their orientations are somewhat more contradictory than complementary.

As mentioned, Entity Framework Code First does a really fantastic job allowing developers to define highly functional object models which it near-magically translates into a functional database.

One of the great features of Entity Framework 4.x Code First is the ability to navigate from a parent object to a child AND navigate from a child object back to its parent. These two-way associations are a cornerstone of EF. They save a tremendous amount of time and greatly simplify development. Moreover, Microsoft has repeatedly touted this functionality as great reason to use Entity Framework.

In Scott Guthrie's, blog post (http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx) where he initially introduced and explained EF 4 Code First approach, he demonstrates the concept of two-way navigation with the following two classes:

public class Dinner
{
    public int DinnerID { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }

    public virtual ICollection<RSVP> RSVPs { get; set; }
}

public class RSVP
{
    public int RsvpID { get; set; }
    public int DinnerID { get; set; }
    public string AttendeeEmail { get; set; }
    public virtual Dinner Dinner { get; set; }
}

As you can see, Dinner contains an association with RSVPs AND RSVPs contains an association to Dinner. There are countless other examples of this on the Internet occurring in many variations.

Because these two way associations are such a core feature of Entity Framework, a reasonable person might expect that Microsoft would support this functionality in the library (Upshot.js) it uses to bring data from a .NET server application to the client. If the functionality was not supported, that is likely something they would want to share as it would significantly key architectural decisions and would most like not work with any properly designed EF 4 Code First implementation.

In my test code (listed in the original question above), I naturally assumed normal EF Code-First functionality (like two-way binding/navigation) was supported because that is what the presentation appeared to show.

However, I immediately started receiving nasty little run-time errors like:

"Object graph for type 'System.Collections.Generic.HashSet`1[[KnockoutTest.Models.Person, KnockoutTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled."

I tried numerous different approaches to try and fix the problem. Based on my ideas and my reading, here are a few of the failed solutions I attempted.

  • Removed the association from one side of the relationship. This is NOT a good solution because it is very handy to be able navigate in each direction between parent and child. (This is probably why these associative properties are referred to as navigation properties.) Remove the relationship from either side had side effects. When the relationship was removed from the parent, the ability to navigate a list of children was also removed. When the relationship was removed from the child, .NET provided me with another friendly error.

"Unable to retrieve association information for association 'KnockoutTest.Models.Client_Persons'. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information."

  • Just in case the issue was the result of the system becoming confused about there being a foreign key, I explicitly specified a [ForeignKey] attribute on the child entity. Everything compiles but .NET returns the "Object graph for type... contains cycles and cannot be serialized..."

  • Some of my reading indicated that adding an attribute like [DataContract(IsReference = true)] in WCF might keep .NET from getting confused about cyclical references. That's when I get this beauty.

"The type 'KnockoutTest.Models.Person' cannot be serialized to JSON because its IsReference setting is 'True'. The JSON format does not support references because there is no standardized format for representing references. To enable serialization, disable the IsReference setting on the type or an appropriate parent class of the type."

This error is very important because it basically tells us that we NOT going to be able to use Upshot AND Entity Framework Code-First together in their normal configuration. Why? Entity Framework is designed to utilize two-way binding. However, when two-way binding is implemented, Upshot says that it cannot handle cyclical references. When cyclical references are managed, Upshot basically says that it cannot handle references between parent and child objects because JSON doesn't support it.

When I watched Steve's demo, I recalled that he DID have a relationship between Customers and Deliveries. I decided to go back and take a much closer look at his object model.

public class Customer
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
}

public class Delivery
{
    // Primary key, and one-to-many relation with Customer
    public int DeliveryId { get; set; }
    public virtual int CustomerId { get; set; }
    public virtual Customer Customer { get; set; }

    // Properties for this delivery
    public string Description { get; set; }
    public bool IsDelivered { get; set; } // <-- This is what we're mainly interested in

What we find is that in Steve's demo is that his relationship only goes one way, and it binds the child to the parent rather than the parent to the children.

In this demo, it kind of works. However, in many real-world applications, this approach makes data access impractical. Take, for example, the demo scenario I included in my original question. We have:

Clients
    Projects
    Persons
        Addresses
        PhoneNumbers

Most developers, I think would not want to start their query from addresses or phone numbers. They would expect to be able to select a list of clients or Projects or Persons and then navigate to a list of its descendants.

I am not 100% certain that is impossible to use entities which have two-way binding enabled, but I am unaware of any configuration that is likely to yield success using only the Microsoft tools.

Fortunately, I do think there is a solution (which takes care of the cyclic dependency issue) which I plan to test in the next few days. That solution is... JSON.Net.

JSON.Net supports cyclic dependencies and maintains references to child objects. If it works as expected, it will take care of two of the errors I got in my testing.

Once I have tested, I will report results here.

I think Steve's demo was brilliant, and I loved his demo. I believe Knockout is amazing. I am very thankful for where Microsoft seems to be going. If there are noteworthy limitations in the tool, I think Microsoft maybe should have been more forthcoming with them.

I don't mean to be overly critical of Microsoft (and definitely am not critical of Steve at all) because I think they have done a wonderful job. I love what upshot promises and am looking forward to seeing where it goes.

I would really love to see someone take upshot and re-factor it (and the WebAPI) so that it can integrate fully with Entity Framework without the use of a third-party tool.

I don't know if a similar tool exists for NHiberbnate, but I would love to maybe even see someone extend upshot to integrate with (or develop a similar library for) NHibernate. By extend, I am primarily talking about consuming the meta-data from NHibernate.

When I test JSON.Net, I also plan to test NHibernate as well.

这篇关于什么会使Entity Framework / Upshot相信我的对象图“包含循环”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆