简体   繁体   中英

Relationships, Interfaces and Entity Framework

What is the best practice for building relationships and interfaces with EF?

I have a set of existing TDD classes and interfaces and a requirement to use Entity Framework Code First as the ORM. I have experience with EF but am unhappy with the way one-to-many and many-to-many relationships are defined.

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.

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.

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. 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. 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. 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. 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:

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... :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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