简体   繁体   English

关系、接口和实体框架

[英]Relationships, Interfaces and Entity Framework

What is the best practice for building relationships and interfaces with EF?与 EF 建立关系和接口的最佳实践是什么?

I have a set of existing TDD classes and interfaces and a requirement to use Entity Framework Code First as the ORM.我有一组现有的 TDD 类和接口,并且需要使用实体框架代码优先作为 ORM。 I have experience with EF but am unhappy with the way one-to-many and many-to-many relationships are defined.我有使用 EF 的经验,但对定义一对多和多对多关系的方式不满意。

I am surprised that this does not appear more dominantly documented (assuming I am not searching for the incorrect terminology).我很惊讶这似乎没有更多的主要记录(假设我没有搜索不正确的术语)。

For example, I have:例如,我有:

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

and

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

I am unsure if I am on the right lines here, any thoughts please?我不确定我在这里是否正确,有什么想法吗?

I'm sure you already know bit since it isn't in your sample so i thought I would mention it: don't forget to your empty constructors.我相信你已经知道了一点,因为它不在你的样本中,所以我想我会提到它:不要忘记你的空构造函数。 EF Code first uses these to fill your model. EF Code 首先使用这些来填充您的模型。

However, you probably don't want them exposed so make them protected instead of public.但是,您可能不希望它们公开,因此将它们设置为受保护而不是公开。 EF will still see a protected constructor but it will not see a private constructor. EF 仍会看到受保护的构造函数,但不会看到私有构造函数。

Your 1 to Many relationship looks fine to me although if you want lazy loading to work it should be virtual.你的一对多关系对我来说看起来不错,尽管如果你想让延迟加载工作它应该是虚拟的。

With your many to many, my preference is to define the joining entity.对于多对多,我的偏好是定义加入实体。 I believe EF will construct a join table behind the scenes for you with what you have.我相信 EF 会用你拥有的东西在幕后为你构建一个连接表。 However I like to keep my navigation properties one sided to avoid possible recursion issues parsing the data later on.但是,我喜欢将我的导航属性放在一边,以避免稍后解析数据时可能出现的递归问题。

I'll edit this with some examples based on your examples once I get to a computer.一旦我使用计算机,我将根据您的示例使用一些示例对其进行编辑。

OK Here is an example for one to many... I have to run but will post many to many this evening but the pattern is pretty simple to expand on:好的,这是一对多的示例......我必须运行,但今晚将发布多对多,但扩展模式非常简单:

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
} 

EDIT: I originally with it since the example had it, but I normally would not specify properties in the interface.编辑:我最初使用它,因为示例中有它,但我通常不会在界面中指定属性。 Typically an interface should be thought of as describing behavior not structure.通常,接口应该被认为是描述行为而不是结构。 In fact Entity Frame work will not be able to work with the Interfaces of properties ( public virtual ICollection bars ) because it needs to have that constructor to be able to fill it with, and of course an interface has no constructor.事实上,实体框架工作将无法与属性的接口(公共虚拟 ICollection 栏)一起工作,因为它需要具有该构造函数才能填充它,当然接口没有构造函数。 I have updated the example accordingly and made the interfaces more behavior driven.我相应地更新了示例并使界面更加行为驱动。 I'll still add a many to many example this evening.今天晚上我仍然会添加一个多对多的例子。

OK, here is the many to many.好的,这里是多对多。 Some highlights: I combined the Foo and Bar interfaces into one, since they both had the same behavior, this is a bit more generic that way.一些亮点:我将 Foo 和 Bar 接口合二为一,因为它们都具有相同的行为,所以这种方式更通用一些。 Note how you can't directly traverse all the way to foo from a bar with foobar (you get a list of Id's essentially), this prevents running into circular references.请注意,您不能直接从带有 foobar 的 bar 一直遍历到 foo (您基本上会获得 Id 列表),这可以防止遇到循环引用。 You can always add a list of one or the other on either side and manually fill it from your repository if desired.您始终可以在任一侧添加一个列表,并根据需要从您的存储库中手动填充它。

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

Also, keep in mind that were not using naming conventions so you'll either have to decorate or add fluent maps (recommended) in order for EF to recognize the keys and build the relationships... if they were using EF's naming convention this could be partially avoided but you still need to add a map for FooBar's multi part key such as:另外,请记住,没有使用命名约定,因此您必须装饰或添加流畅的映射(推荐),以便 EF 识别键并建立关系......如果他们使用 EF 的命名约定,这可能可以部分避免,但您仍然需要为 FooBar 的多部分键添加映射,例如:

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

and then add that to your context configurations.然后将其添加到您的上下文配置中。

Hope that helps... :)希望有帮助... :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM