13

初めてサイトにアクセスしたので、タグ付けが間違っていたり、他の場所で回答があった場合は申し訳ありません...

私は現在のプロジェクトで特定の状況に遭遇し続けています. パターンは次のとおりです。子のコレクションを持つ親であり、親は子コレクション内の特定の項目 (通常は「既定の」子) への 1 つ以上の参照を持っています。

より具体的な例:

public class SystemMenu 
{
    public IList<MenuItem> Items { get; private set; }
    public MenuItem DefaultItem { get; set; }
}

public class MenuItem
{
    public SystemMenu Parent { get; set; }
    public string Name { get; set; }
}

私には、これは関係をモデル化するためのきれいな方法のように思えますが、循環関連のおかげですぐに問題が発生します。循環外部キーのために DB で関係を強制できず、LINQ to SQL が原因で爆発します。循環関連。これを回避できたとしても、それは明らかに良い考えではありません。

現在、私の唯一のアイデアは、MenuItem に「IsDefault」フラグを設定することです。

public class SystemMenu 
{
    public IList<MenuItem> Items { get; private set; }
    public MenuItem DefaultItem 
    {
        get 
        {
            return Items.Single(x => x.IsDefault);
        }
        set
        {
            DefaultItem.IsDefault = false;
            value.DefaultItem = true;
        }
    }
}

public class MenuItem
{
    public SystemMenu Parent { get; set; }
    public string Name { get; set; }
    public bool IsDefault { get; set; }
}

誰かが同様のことを扱い、アドバイスを提供できますか?

乾杯!

編集:これまでの回答に感謝します。おそらく「メニュー」の例は素晴らしいものではありませんでしたが、代表的なものを考えようとしていたので、自明ではないドメインの詳細に入る必要はありませんでしたモデル!おそらくより良い例は、会社/従業員の関係でしょう:

public class Company
{
    public string Name { get; set; }
    public IList<Employee> Employees { get; private set; }
    public Employee ContactPerson { get; set; }
}

public class Employee
{
    public Company EmployedBy { get; set; }
    public string FullName { get; set; }
}

従業員は間違いなく会社への参照を必要とし、各会社は 1 つの ContactPerson しか持つことができません。これにより、私の元のポイントが少し明確になることを願っています!

4

8 に答える 8

25

これを解決する秘訣は、親が子のすべてのメソッドを知る必要はなく、子が親のすべてのメソッドを知る必要がないことを理解することです。したがって、インターフェイス分離の原則を使用してそれらを分離できます。

つまり、子が必要とするメソッドのみを持つ親用のインターフェースを作成します。また、親が必要とするメソッドのみを持つ子用のインターフェースも作成します。次に、親に子インターフェースのリストを含め、子が親インターフェースを指すようにします。UML ダイアグラムが Eckles-Jordan フリップフロップのジオメトリを持っているため、私はこれを Flip Flob パターンと呼んでいます (訴えてください、私は古いハードウェア エンジニアです!)

  |ISystemMenu|<-+    +->|IMenuItem|
          A    1  \  / *     A
          |        \/        |
          |        /\        |
          |       /  \       |
          |      /    \      |
          |     /      \     |
    |SystemMenu|        |MenuItem|

この図にはサイクルがないことに注意してください。あるクラスから始めて、矢印に従って出発点に戻ることはできません。

場合によっては、分離を正しく行うために、いくつかのメソッドを移動する必要があります。SystemMenu にあるはずだったコードを MenuItem に移動するなどのコードがあるかもしれません。しかし、一般的にこの手法はうまく機能します。

于 2009-03-07T17:16:30.563 に答える
5

あなたの解決策はかなり合理的なようです。

考慮すべきもう1つのことは、メモリ内のオブジェクトがデータベーススキーマと正確に一致する必要はないということです。データベースでは、子プロパティを使用してより単純なスキーマを作成できますが、メモリでは、物事を最適化し、子オブジェクトへの参照を使用して親を作成できます。

于 2009-03-05T18:42:57.567 に答える
3

あなたの問題がよくわかりません。明らかに、オブジェクトをインスタンスではなく参照として保持する C# を使用しています。これは、相互参照や自己参照を使用してもまったく問題がないことを意味します。

オブジェクトがより複合化されているC++およびその他の言語では、通常は参照またはポインターを使用して解決される問題が発生する可能性がありますが、C#は問題ありません。

おそらくあなたの問題は、何らかの方法ですべての参照をたどろうとしていて、循環参照につながることです。LINQ は、遅延読み込みを使用してこの問題に対処します。たとえば、LINQ は Company または Employee を参照するまでロードしません。そのような参照を 1 レベル以上たどらないようにする必要があります。

ただし、2 つのテーブルを相互の外部キーとして実際に追加することはできません。そうしないと、従業員を削除するには最初に会社を削除する必要があるため、レコードを削除することはできませんが、従業員を削除せずに会社を削除することはできません。 . 通常、この場合、実際の外部キーとして 1 つだけを使用し、もう 1 つは単に疑似 FK (つまり、FK として使用されるが、制約が有効になっていないもの) になります。どちらがより重要な関係であるかを決定する必要があります。

会社の例では、会社ではなく従業員を削除する可能性が高いため、会社 -> 従業員 FK を制約関係にします。これにより、従業員がいる場合に会社を削除することはできなくなりますが、会社を削除せずに従業員を削除することはできます。

また、このような状況では、コンストラクターで新しいオブジェクトを作成しないでください。たとえば、Employee オブジェクトが新しい Company オブジェクトを作成し、その中にその従業員用に作成された新しい employee オブジェクトが含まれている場合、最終的にはメモリを使い果たします。代わりに、作成済みのオブジェクトをコンストラクターに渡すか、構築後にオブジェクトを設定します。おそらく初期化メソッドを使用します。

例えば:

Company c = GetCompany("ACME Widgets");
c.AddEmployee(new Employee("Bill"));

次に、AddEmployee で会社を設定します

public void AddEmployee(Employee e)
{
    Employees.Add(e);
    e.Company = this;
}
于 2009-03-07T17:41:46.043 に答える
2

自己参照のGoFコンポジットパターンがここでの注文かもしれません。メニューにはリーフMenuItemのコレクションがあり、両方に共通のインターフェースがあります。そうすれば、MenusやMenuItemsからMenuを作成できます。スキーマには、それ自体の主キーを指す外部キーを持つテーブルがあります。そのようにウォーキングメニューでも機能します。

于 2009-03-05T18:45:55.610 に答える
1

ドメイン駆動設計の意味で、可能な場合はエンティティ間の双方向の関係を回避することを選択できます。リレーションを保持するために1つの「集約ルート」を選択し、集約ルートからナビゲーションする場合にのみ他のエンティティを使用します。可能な限り双方向の関係を避けようとしています。YAGNIのおかげで、「鶏が先か卵が先か」という質問が出てきます。それでも双方向の関連付けが必要な場合は、前述のソリューションの1つを選択してください。

/// This is the aggregate root
public class Company
{
    public string Name { get; set; }
    public IList<Employee> Employees { get; private set; }
    public Employee ContactPerson { get; set; }
}

/// This isn't    
public class Employee
{
    public string FullName { get; set; }
}
于 2009-03-08T12:13:26.480 に答える
1

コードでは、両方の方法で物事を参照するには、両方の方法で参照する必要があります。しかし、データベースでは、物事を機能させるために参照が必要なのは 1 つの方法だけです。結合の仕組みにより、テーブルの 1 つに外部キーがあれば十分です。考えてみると、データベース内のすべての外部キーが反転し、循環参照を作成および作成する可能性があります。この場合、おそらく親への外部キーを持つ子レコードを 1 つだけ選択するのが最善です。

于 2009-03-07T17:56:11.950 に答える
0

2つのテーブルが相互に参照するデータベースで外部キーを適用できます。2つの方法が思い浮かびます。

  1. 親のデフォルトの子列は最初はnullであり、すべての子行が挿入された後にのみ更新されます。
  2. 制約チェックはコミット時まで延期します。これは、最初に子への参照が壊れている親を最初に挿入してから、子を挿入できることを意味します。遅延制約チェックの問題の1つは、コミット時にデータベース例外がスローされる可能性があることです。これは、多くのdbフレームワークでは不便なことがよくあります。また、挿入する前に子の主キーを知っておく必要があることを意味します。これは、セットアップで扱いにくい場合があります。

ここでは、親メニュー項目が1つのテーブルにあり、子が別のテーブルにあると想定していますが、両方が同じテーブルにある場合は同じソリューションが機能します。

多くのDBMSは、遅延制約チェックをサポートしています。使用しているDBMSについては言及していませんが、おそらくあなたもそうです

于 2009-03-08T12:26:29.240 に答える
0

回答してくれたすべての人に感謝します。いくつかの本当に興味深いアプローチです! 結局、私は大急ぎで何かを成し遂げなければならなかったので、これが私が思いついたものです:

と呼ばれる 3 番目のエンティティWellKnownContactと対応するWellKnownContactType列挙型が導入されました。

public class Company
{
    public string Name { get; set; }
    public IList<Employee> Employees { get; private set; }
    private IList<WellKnownEmployee> WellKnownEmployees { get; private set; }
    public Employee ContactPerson
    {
        get
        {
            return WellKnownEmployees.SingleOrDefault(x => x.Type == WellKnownEmployeeType.ContactPerson);
        }
        set
        {                
            if (ContactPerson != null) 
            {
                // Remove existing WellKnownContact of type ContactPerson
            }

            // Add new WellKnownContact of type ContactPerson
        }
    }
}

public class Employee
{
    public Company EmployedBy { get; set; }
    public string FullName { get; set; }
}

public class WellKnownEmployee
{
    public Company Company { get; set; }
    public Employee Employee { get; set; }
    public WellKnownEmployeeType Type { get; set; }
}

public enum WellKnownEmployeeType
{
    Uninitialised,
    ContactPerson
}

少し面倒に感じますが、循環参照の問題を回避し、DB にきれいにマップします。これにより、LINQ to SQL で巧妙すぎることを実行しようとする手間が省けます! また、複数のタイプの「よく知られている連絡先」が可能になります。これは、次のスプリントで確実に行われます (実際には YAGNI ではありません!)。

興味深いことに、不自然な会社/従業員の例を思いつくと、実際に扱っているかなり抽象的なエンティティとは対照的に、考えるのがずっと簡単になりました。

于 2009-03-09T14:15:53.947 に答える