【问题标题】:Relationships, Interfaces and Entity Framework关系、接口和实体框架
【发布时间】:2016-05-05 11:08:16
【问题描述】:

与 EF 建立关系和接口的最佳做法是什么?

我有一组现有的 TDD 类和接口,并且需要使用 Entity Framework Code First 作为 ORM。我有使用 EF 的经验,但对定义一对多和多对多关系的方式不满意。

令我惊讶的是,这似乎没有更多地记录在案(假设我没有在搜索不正确的术语)。

例如,我有:

public interface IBar {
      string BarValue { get; set; }
} 
public class Bar : IBar {
      string BarValue { get; set; }
} 


public interface IFoo {
      string FooValue { get; set; }
      ICollection<IBar> Bars { get; set; }
} 

public class Foo : IFoo {

      public Foo()
      {
          Bars = new List<Bar>();
      }
      public string FooValue { get; set; }
      public virtual ICollection<IBar> Bars { get; set; }
} 

public interface IBar {
      string BarValue { get; set; }
      ICollection<IFoo> Foos { get; set; }
} 
public class Bar : IBar {
      public Bar()
      {
          Foos = new List<Foo>();
      }
      string BarValue { get; set; }
      public virtual ICollection<IFoo> Foos { get; set; }
} 


public interface IFoo {
      string FooValue { get; set; }
      ICollection<IBar> Bars { get; set; }
} 

public class Foo : IFoo {
      public Foo()
      {
           Bars = new List<Bar>();
      }
      public string FooValue { get; set; }
      public virtual ICollection<IBar> Bars { get; set; }
} 

我不确定我在这里是否正确,请问有什么想法吗?

【问题讨论】:

  • EF 不支持接口。它不能用ICollection&lt;IFoo&gt; 之类的东西做任何事情。但不清楚您想在 EF 中映射什么以及使用这些接口期望什么。

标签: c# entity-framework ef-code-first code-first


【解决方案1】:

我确定您已经知道一点,因为它不在您的示例中,所以我想我会提到它:不要忘记您的空构造函数。 EF 代码首先使用这些来填充您的模型。

但是,您可能不希望它们暴露出来,因此请将它们设为受保护而不是公开。 EF 仍会看到受保护的构造函数,但不会看到私有构造函数。

你的一对多关系在我看来很好,但如果你想让延迟加载工作,它应该是虚拟的。

对于您的多对多,我的偏好是定义加入实体。我相信 EF 会在幕后为您构建一个连接表,并使用您所拥有的。但是,我喜欢将导航属性保持在一侧,以避免稍后解析数据时可能出现的递归问题。

一旦我使用计算机,我将根据您的示例使用一些示例对其进行编辑。

好的,这是一对多的示例...我必须运行,但今晚会发布多对多,但扩展模式非常简单:

public interface IBar
{
    string barValue { get; }        // Values can sometimes be valuable in interfaces, see Foo's AddBar behavior.
    void SetValue(string value); // Interfaces work best describing behavior, not structure.
}
public class Bar : IBar
{
    public string barValue { get; private set; }

    protected Bar() : this("") { } // EF
    private Bar(string value)      // Private Constructor
    {
        barValue = value;
    }

    public static Bar Create(string value = "") // Factory
    {
        return new Bar(value);
    }
}


public interface IFoo
{
    void SetValue(string value);
    void AddBar(IBar bar);
}

public class Foo : IFoo
{
    // Our Properties... note that private set does not interfere with EF at all, and helps encapsulate.
    public string fooValue { get; private set; }
    public virtual ICollection<Bar> bars { get; private set; }

    // Constructor for EF, protected since we probably dont want to ever use it ourselves.
    protected Foo() : this("") { }

    // Our internal constructor, we will expose a factory for creating new instances (better control).
    private Foo(string value)
    {
        fooValue = value;
        bars = new List<Bar>();
    }

    // Our factory, great place to validate parameters, etc before even constructing the object.
    public static Foo Create(string value = "")
    {
        return new Foo(value);
    }

    // Methods for controling Foo's properties:
    public void SetValue(string value) { this.fooValue = value; }
    public void AddBar(IBar bar) { this.bars.Add(Bar.Create(bar.value)); }  // Leveraging Bar's factory
} 

编辑:我最初使用它,因为示例有它,但我通常不会在界面中指定属性。通常应该将接口视为描述行为而不是结构。事实上,实体框架工作将无法使用属性的接口(公共虚拟 ICollection 条),因为它需要具有该构造函数才能填充它,当然接口没有构造函数。我已经相应地更新了示例,并使界面更加受行为驱动。今天晚上我仍然会添加多对多的例子。

好的,这里是多对多。一些亮点: 我将 Foo 和 Bar 接口合二为一,因为它们都具有相同的行为,所以这种方式更通用。请注意,您不能使用 foobar 直接从 bar 一路遍历到 foo(本质上您会得到一个 Id 列表),这可以防止遇到循环引用。您始终可以在任一侧添加一个或另一个列表,并根据需要从您的存储库中手动填充它。

public interface IFooBar
{
    string value { get; }
    void SetValue(string value);
    void AddFooBar(IFooBar item);
}


public class Bar : IFooBar
{
    public string value { get; private set; }
    public virtual ICollection<FooBar> foos { get; private set; }

    protected Bar() : this("") { } 
    private Bar(string v) { 
        value = v;
        foos = new List<FooBar>();
    }

    public static Bar Create(string value = "") { return new Bar(value); }

    public void SetValue(string value) { this.value = value; }

    public void AddFooBar(IFooBar foo) { this.foos.Add(FooBar.Create(this.value, foo.value)); }
}

public class FooBar
{
    // assuming fooValue and barValue are the keys for Foo and Bar.
    public String fooValue { get; private set; }
    public String barValue { get; private set; }

    protected FooBar() { }
    private FooBar(String foo, String bar) {
        fooValue = foo;
        barValue = bar;
    }

    public static FooBar Create(String fooValue, String barValue) { return new FooBar(fooValue, barValue); }
}


public class Foo : IFooBar
{
    public string value { get; private set; }
    public virtual ICollection<FooBar> bars { get; private set; }

    protected Foo() : this("") { }

    private Foo(string v)
    {
        value = v;
        bars = new List<FooBar>();
    }

    public static Foo Create(string value = "") { return new Foo(value); }

    public void SetValue(string value) { this.value = value; }

    public void AddFooBar(IFooBar bar) { this.bars.Add(FooBar.Create(this.value, bar.value)); }
} 

另外,请记住,没有使用命名约定,因此您必须装饰或添加流畅的映射(推荐),以便 EF 识别键并建立关系......如果他们使用 EF 的命名约定这可以部分避免,但您仍然需要为 FooBar 的多部分键添加映射,例如:

public class FooBarMap: EntityTypeConfiguration<FooBar>
{
    public FooBarMap()
    {
        HasKey(k => new { k.barValue, k.fooValue });
    }
}

然后将其添加到您的上下文配置中。

希望对您有所帮助... :)

【讨论】:

  • 哦,自己定义交叉连接实体的另一个好处是,现在您可以根据需要向其添加其他属性,例如关系何时形成,甚至使其成为有效日期关系。
猜你喜欢
  • 1970-01-01
  • 2014-03-25
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多