1

Knockout2.1.0とUpshot1.0.0.2をEntityFramework4.3(Code-First)でテストしていますが、次のエラーが発生しています。

{"タイプ'System.Collections.Generic.HashSet`1[[KnockoutTest.Models.Person、KnockoutTest、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null]]'のオブジェクトグラフにはサイクルが含まれており、参照されている場合はシリアル化できません追跡が無効になっています。"}

私はいくつかの基本的な親子エンティティでテストするためにかなり典型的なモデルを使用しています。

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

私の文脈は非常に一般的です。

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)
    {
    }
}

サンプルデータも少しありますが、関連性はないはずです。

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

    }

基本的に、Client、Person、Projectなどから「インクルード」を使用しようとすると、上記と同様のエラーが発生します。

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

.NETがこのモデルについて不平を言うべき理由を理解できますか?

とにかく、それを回避するにはどのようなオプションが必要ですか?

助けてくれてありがとう!

4

2 に答える 2

15

簡単に言うと、Steve SandersonによるKnockout、Upshot、およびEntity Framework 4.xコードのデモンストレーション-最初にシングルページアプリケーションを構築したのは(すばらしい!!!)、少し誤解を招く可能性があります。これらのツールは、一見しただけではうまく機能しません。[ネタバレ:合理的な回避策があると思いますが、Microsoftの領域の外に少し足を踏み入れる必要があります。]

(Steveの素晴らしいシングルページアプリケーション(SPA)のプレゼンテーションについては、http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2159にアクセスしてください。一見の価値があります。)

ほとんどのWebアプリケーションでは、概念的に次の方法でデータを移動および操作する必要があります。

データソース(多くの場合データベース)->Webアプリケーション->ブラウザクライアント

ブラウザクライアント->Webアプリケーション->データソース(多くの場合データベース)

以前は、データを操作してデータベースとの間でデータを送受信することは、非常に悪夢でした。.NET 1.0 / 1.1日以内にいる必要がある場合は、次のような手順を含む開発プロセスを思い出してください。

  • データモデルを手動で定義する
  • すべてのテーブルの作成、関係の設定、インデックスと制約の手動定義など。
  • データにアクセスするためのストアドプロシージャの作成とテスト-通常、各プロシージャに含める各フィールドを手動で指定します。
  • データを保持するPOCO(Plain Old CLR Objects)を作成します
  • データベースへの接続を開き、返された各レコードを繰り返し繰り返し、それをPOCOオブジェクトにマップするコード。

これは、データをシステムに取り込むためだけのものでした。逆に戻ると、これらの手順のいくつかを逆の順序で繰り返す必要がありました。重要なのは、データベースのコーディングには非常に時間がかかった(そして本当に非常に退屈だった)ということです。明らかに、番号コード生成やその他のツールが登場し、物事を簡素化しました。

真のブレークスルーは、NHibernate、Entity Framework 4(Code-Firstアプローチ)、および開発者からデータベースを(ほぼ)完全に抽象化した他の同様のORMツールでした。これらのツールは、開発速度を向上させるだけでなく、誤ってバグを導入する機会が少なかったため、全体的なコード品質も向上させました。

現在、多くのアプリケーションでは、データベースの詳細のほとんどが隠されているため、データベースへの接続とデータベースとの相互作用は(ほとんど)後付けです。

Microsoftはまた、Upshot.jsとWebAPIに、これら2つのツールを相互に組み合わせて使用​​すると、NHibernateとEntity Framework 4が行ったのと同じ方法で、サーバーとブラウザー間の通信に革命をもたらすという考えを提供しました。サーバーとデータベースの間。

これは確かに非常に価値のある成果です。特に、クライアントがよりインタラクティブなWebアプリケーションを求めているためです。

開発者がより多くのユーザーインターフェイスを(ブラウザー)クライアントに移動することを妨げる主な障害の1つは、大量のコーディングが必要になることです。いくつかの手順は次のとおりです。

  • データをクライアントに送信します(通常はJSON形式で)
  • .NETオブジェクトのすべてのプロパティをJavaScriptオブジェクトにマップします
  • オブジェクトとそのプロパティに関するすべてのメタデータを再作成します
  • そのデータをクライアントブラウザの要素にバインドします
  • 変更を監視する
  • データが変更されたら、(サーバーに送り返すために)データを再マップします
  • データをサーバーに送り返します

これは、データベースにデータを出し入れするための従来のプロセスと複雑さが非常に似ているため、実際には「既視感」のように見えます。

Webアプリケーションの構成方法によっては、サーバーに戻ったデータを実際のデータベースオブジェクトにマッピングすることも楽しい場合があります。(これは、ほとんどの場合に当てはまります。)

このサーバー->クライアント->サーバーデータ送信には多くのコーディングが必要であり、予期しない課題が発生する可能性があります。JavaScriptをデバッグするのがどれほど楽しいかを忘れないでください。(わかりました。数年前よりも苦痛は少なくなりましたが、Visual StudioでC#コードをデバッグするほど開発者にとって使いやすいものではありません。)

シングルページアプリケーションに関するSteveSandersonのプレゼンテーションは、はるかに異なる(そしてより優れた)ソリューションを提供します。

WebAPI、Upshot.js、およびKnockoutは、高度にインタラクティブなユーザーエクスペリエンスを提供しながら、ブラウザークライアントとの間でシームレスにデータを配信および受信できるようになるという考え方です。わお!それはあなたがただ誰かに手を差し伸べて抱きしめたいと思わせませんか?

このアイデアは新しいものではありませんが、.NETで実際にこれを行うために私が見た最初の真剣な取り組みの1つです。

データがWebAPIを介して配信され、(Upshotを介して)クライアントに到達すると、Knockoutなどのフレームワークはデータを消費し、最先端のWebアプリケーションが必要とする非常に高レベルの双方向性を提供できるようになります。(すぐにはわからないかもしれませんが、私が説明しているのは、主に「ページ」をロードすることによって機能するのではなく、主にAJAX要求を介してJSON形式のデータを通信することによって機能するアプリケーションです。)

このコーディングのすべてを削減するツールは、明らかに開発者コミュニティにすぐに受け入れられるでしょう。

Upshot.js(RIA / JSの名前が変更され、アップグレードされたバージョン)は、上記のありふれたタスクのいくつかを処理することになっています。これは、WebAPIとKnockoutの間の接着剤であると考えられています。これは、.NETからJSONまたはXMLで送信されるオブジェクトを動的にマッピングし、オブジェクトのプロパティ、必須フィールド、フィールドの長さ、表示名、説明などに関連するメタデータを公開することを目的としています(メタデータはマッピングを許可し、検証で使用するためにアクセスできるもの。)

注:アップショットのメタデータにアクセスして、jQuery検証やKnockout検証プラグインの1つなどの検証フレームワークに関連付ける方法はまだわかりません。これは私のやることリストにあります。

注:これらのタイプのメタデータのどれがサポートされているかはわかりません。これは私のやることリストにあります。補足として、System.ComponentModel.DataAnnotationsの外部でメタデータを試して、NHibernate属性とカスタム属性がサポートされているかどうかも確認する予定です。

したがって、これらすべてを念頭に置いて、Steveが実際のWebアプリケーションのデモで使用したのと同じテクノロジのセットを使用することにしました。これらが含まれます:

  • コードファーストアプローチを使用したEntityFramework4.3
  • WebAPIを使用したASP.NETMVC4
  • Upshot.js
  • Knockout.js

これらのテクノロジはすべて、a)最新のMicrosoftツール(オープンソースのノックアウトを除く)であり、現在MicrosoftのSteve Sandersonが、開発を示す主要なMicrosoftプレゼンテーションで一緒に使用したため、一緒に機能することが期待されます。シングルページアプリケーションの。

残念ながら、実際に私が見つけたのは、Entity Framework 4.xとUpshot.jsは世界を非常に異なる方法で表示し、それらの方向は補完的というよりもいくぶん矛盾しているということでした。

前述のように、Entity Framework Code Firstは非常に素晴らしい仕事をしており、開発者は非常に機能的なオブジェクトモデルを定義でき、魔法のように機能的なデータベースに変換されます。

Entity Framework 4.x Code Firstの優れた機能の1つは、親オブジェクトから子に移動し、子オブジェクトからその親に戻る機能です。これらの双方向の関連付けは、EFの基礎です。それらは途方もない時間を節約し、開発を大幅に簡素化します。さらに、Microsoftは、Entity Frameworkを使用する大きな理由として、この機能を繰り返し宣伝してきました。

Scott Guthrieのブログ投稿(http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx)で、彼は最初にEFを紹介して説明しました。 4コードファーストアプローチでは、次の2つのクラスを使用して双方向ナビゲーションの概念を示します。

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

ご覧のとおり、DinnerにはRSVPとの関連付けが含まれ、RSVPにはDinnerとの関連付けが含まれています。インターネット上には、さまざまなバリエーションで発生するこの例が他にも無数にあります。

これらの双方向の関連付けはEntityFrameworkのコア機能であるため、合理的な人は、Microsoftが.NETサーバーアプリケーションからクライアントにデータを取り込むために使用するライブラリ(Upshot.js)でこの機能をサポートすることを期待するかもしれません。機能がサポートされていない場合、それはアーキテクチャ上の決定を大きく左右し、適切に設計されたEF4コードファーストの実装では機能しないことを望んでいるため、共有したいと思う可能性があります。

私のテストコード(上記の元の質問にリストされている)では、通常のEFコードファースト機能(双方向バインディング/ナビゲーションなど)がサポートされていると当然想定していました。これは、プレゼンテーションが表示されているように見えるためです。

しかし、私はすぐに次のような厄介な小さな実行時エラーを受け取り始めました。

「タイプ'System.Collections.Generic.HashSet`1[[KnockoutTest.Models.Person、KnockoutTest、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null]]'のオブジェクトグラフにはサイクルが含まれており、参照追跡の場合はシリアル化できません無効になっています。"

問題を解決するために、さまざまなアプローチを試しました。私の考えと私の読書に基づいて、ここに私が試みた失敗した解決策のいくつかがあります。

  • 関係の片側から関連付けを削除しました。親と子の間で各方向にナビゲートできると非常に便利なので、これは良い解決策ではありません。(これが、これらの関連プロパティがナビゲーションプロパティと呼ばれる理由です。)どちらかの側から関係を削除すると、副作用が発生しました。リレーションシップが親から削除されると、子のリストをナビゲートする機能も削除されました。関係が子から削除されたとき、.NETは私に別のフレンドリーなエラーを提供しました。

「アソシエーション'KnockoutTest.Models.Client_Persons'のアソシエーション情報を取得できません。外部キー情報を含むモデルのみがサポートされています。外部キー情報を含むモデルの作成の詳細については、EntityFrameworkのドキュメントを参照してください。」

  • 問題が外部キーの存在についてシステムが混乱した結果である場合に備えて、子エンティティに[ForeignKey]属性を明示的に指定しました。すべてがコンパイルされますが、.NETは「タイプのオブジェクトグラフ...にはサイクルが含まれており、シリアル化できません...」を返します。

  • 私の読書の一部は、WCFに[DataContract(IsReference = true)]のような属性を追加すると、.NETが循環参照について混乱するのを防ぐことができることを示しました。その時、私はこの美しさを手に入れました。

「タイプ'KnockoutTest.Models.Person'は、IsReference設定が'True'であるため、JSONにシリアル化できません。参照を表すための標準化された形式がないため、JSON形式は参照をサポートしません。シリアル化を有効にするには、のIsReference設定を無効にします。タイプまたはタイプの適切な親クラス。」

このエラーは、基本的に、Upshot AND EntityFrameworkCode-Firstを通常の構成で一緒に使用できないことを示しているため非常に重要です。なんで?Entity Frameworkは、双方向バインディングを利用するように設計されています。ただし、双方向バインディングが実装されている場合、Upshotは循環参照を処理できないと言います。循環参照が管理される場合、Upshotは基本的に、JSONがサポートしていないため、親オブジェクトと子オブジェクト間の参照を処理できないと言います。

スティーブのデモを見たとき、彼が顧客と配達の間に関係を持っていたことを思い出しました。私は戻って、彼のオブジェクトモデルをもっと詳しく見ることにしました。

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

スティーブのデモでは、彼の関係は一方向にしか進まず、親を子にバインドするのではなく、子を親にバインドすることがわかりました。

このデモでは、それは一種の作品です。ただし、多くの実際のアプリケーションでは、このアプローチによりデータアクセスが実用的ではなくなります。たとえば、元の質問に含めたデモシナリオを考えてみましょう。我々は持っています:

Clients
    Projects
    Persons
        Addresses
        PhoneNumbers

ほとんどの開発者は、住所や電話番号からクエリを開始したくないと思います。クライアント、プロジェクト、または個人のリストを選択して、その子孫のリストに移動できることを期待します。

双方向バインディングが有効になっているエンティティを使用することが不可能であると100%確信しているわけではありませんが、Microsoftツールのみを使用して成功する可能性のある構成を認識していません。

幸いなことに、私は今後数日でテストする予定の解決策(循環依存の問題を処理する)があると思います。その解決策は... JSON.Netです。

JSON.Netは循環依存関係をサポートし、子オブジェクトへの参照を維持します。期待どおりに機能する場合は、テストで発生した2つのエラーを処理します。

テストが終わったら、ここで結果を報告します。

スティーブのデモは素晴らしかったと思います、そして私は彼のデモが大好きでした。ノックアウトは素晴らしいと思います。私はマイクロソフトがどこに向かっているように見えるかについて非常に感謝しています。ツールに注目すべき制限がある場合は、Microsoftがもっと近づいてくるはずだったと思います。

彼らは素晴らしい仕事をしたと思うので、私はマイクロソフトに過度に批判的であるという意味ではありません(そして間違いなくスティーブに批判的ではありません)。私はアップショットが約束するものが大好きで、それがどこに行くのかを見るのを楽しみにしています。

サードパーティのツールを使用せずにEntityFrameworkと完全に統合できるように、誰かがアップショットを取り、それ(およびWebAPI)をリファクタリングするのを本当に望んでいます。

NHiberbnateに同様のツールが存在するかどうかはわかりませんが、誰かがNHibernateと統合する(または同様のライブラリを開発する)ためにアップショットを拡張するのを見たいと思います。ひいては、私は主にNHibernateからのメタデータの消費について話している。

JSON.Netをテストするときは、NHibernateもテストする予定です。

于 2012-05-25T01:59:06.287 に答える