你如何打破实体之间的循环关联?

Jon*_*n M 13 c# sql oop modeling

我第一次在网站上如果标记错误或在其他地方得到回答,请道歉...

我一直在处理当前项目中的特定情况,我想知道你们如何处理它.模式是:父项具有子集合,父项具有对子集合中特定项的一个或多个引用,通常是"默认"子项.

一个更具体的例子:

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; }
}
Run Code Online (Sandbox Code Playgroud)

对我而言,这似乎是建立关系的一种很好的干净方式,但由于循环关联而立即导致问题,由于循环外键,我无法强制执行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; }
}
Run Code Online (Sandbox Code Playgroud)

有没有人处理类似的事情,可以提供一些建议?

干杯!

编辑:感谢到目前为止的回复,也许"菜单"的例子并不精彩,但是我试图想出一些具有代表性的东西,所以我不必深入研究我们不那么自我解释的领域的细节模型!也许更好的例子是公司/员工关系:

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; }
}
Run Code Online (Sandbox Code Playgroud)

员工肯定需要参考他们的公司,每个公司只能有一个ContactPerson.希望这让我原来的观点更加清晰!

小智 25

解决这个问题的诀窍是要意识到父母不需要知道孩子的所有方法,并且孩子不需要知道父母的所有方法.因此,您可以使用接口隔离原则来解耦它们.

简而言之,您为父级创建了一个界面,该界面只包含子级所需的那些方法.您还为子级创建了一个只有父级需要的方法的接口.然后,您让父级包含子接口的列表,并让子级指向父接口.我将其称为Flip Flob Pattern,因为UML图具有Eckles-Jordan触发器的几何形状(Sue me,我是一位老硬件工程师!)

  |ISystemMenu|<-+    +->|IMenuItem|
          A    1  \  / *     A
          |        \/        |
          |        /\        |
          |       /  \       |
          |      /    \      |
          |     /      \     |
    |SystemMenu|        |MenuItem|
Run Code Online (Sandbox Code Playgroud)

请注意,此图中没有循环.你不能从一个班级开始,然后按箭头回到起点.

有时,为了使分离恰到好处,你必须移动一些方法.可能存在您认为应该在SystemMenu中移动到MenuItem等的代码.但是通常该技术运行良好.


Dav*_*man 5

你的解决方案似乎很合理.

另一件需要考虑的事情是内存中的对象不必与数据库模式完全匹配.在数据库中,您可以使用具有子属性的更简单的模式,但在内存中,您可以优化事物并让父对象具有对子对象的引用.


Jon*_*n M 0

感谢所有回答的人,一些非常有趣的方法!最后我不得不匆忙完成一些事情,所以这就是我想出的:

引入了第三个实体,称为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
}
Run Code Online (Sandbox Code Playgroud)

感觉有点麻烦,但解决了循环引用问题,并干净地映射到数据库,这省去了尝试让 LINQ to SQL 做任何太聪明的事情!还允许多种类型的“众所周知的联系人”,这肯定会在下一个冲刺中出现(所以不是真正的 YAGNI!)。

有趣的是,一旦我想到了人为的公司/员工示例,与我们真正处理的相当抽象的实体相比,它就变得更容易思考。