4

Using the Code First approach with Entity Framework 6.1.3, I'm trying to create and configure my own many-to-many table based on this post. I strongly agree with him: I don't want to follow the conventions, because that limits my design.

Behold the many-to-many with payload class in Code First:

public class FooBar
{
    [Key, Column(Order = 1)]
    public virtual Foo Foo { get; set; }

    [Key, Column(Order = 2)]
    public virtual Bar Bar { get; set; }

    //some more 'payload' properties
}

I'd like to keep it this way without adding the scalar properties. So I don't want these properties in my model:

public int FooId { get; set; }
public int BarId { get; set; }

My question is specifically if it's possible in Entity Framework 6 to have a navigation property like this as a (composite) key? Most answers to this question was based on earlier versions of Entity Framework, and it wasn't possible then. So the question is, is it possible now?

In this answer it's mentioned that:

You need to setup your classes in a specific way so that EF recognises what your trying to achieve.

But it's still unclear to me if it's technically possible. And if yes, could someone provide a sample in code on how to achieve this?

Community
  • 1
  • 1
QuantumHive
  • 5,613
  • 4
  • 33
  • 55
  • 2
    The last answer is EF6 too, and it tells it's not possible (although not very clearly stated, but the information is still up to date). – Gert Arnold Nov 20 '16 at 22:09
  • @GertArnold Ah I see, I've misread the last answer (my bad). Guess I'll have to fallback by adding the scalar properties, unfortunately. – QuantumHive Nov 20 '16 at 22:23

3 Answers3

2

You cannot do that.

Not only with EF 6.X, but it's also not possible in EF Core. It is by design. EF doesn't know how to map the relationships if you'll not define those scalar properties on the junction table. Again I have to say that, it is by design. You don't have any other options here unfortunately. You must include those scalar properties on the junction table.

QuantumHive
  • 5,613
  • 4
  • 33
  • 55
Sampath
  • 63,341
  • 64
  • 307
  • 441
  • Somehow I'm surprised that it's a limitation by design. I've just created this [gist demonstration](https://gist.github.com/QuantumHive/e515a771172e660a686c0cf923062551) to illustrate that you can map foreign keys (in a one-to-one relationship) without declaring a scalar property for it. It strikes me weird that Entity Framework allows us to do this with foreign keys, but *not* with primary keys. Since it's possible with foreign keys, I would've expected to be possible with primary keys too, but alas. – QuantumHive Nov 20 '16 at 22:51
  • you can do that way on the 1:1 relationship. here the problem is, you need to include junction table yourself no. otherwise you can configure it using Fluent API.then you'll not have that extra model on your domain.I mean `FooBar`.here you can see that how to configure M : M without junction table created by yourself. http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx – Sampath Nov 20 '16 at 23:10
1

I hope this will be useful for some.

After hours of doing search on search engines, I finally have made it here. Sadly, there's no answer to that the same issue both you and I were facing. I just did not want to withdraw my design choices and create lots of scalar types as such you told in your post.

I come up with the solution of implementing basic OOP:

// let's assume we would like to have each ObjectModel with
// unique Name and Class properties
// this two data will identify the whole row. (i.e ShadowHawk:Bow)

class ObjectModel {

   public string Name { get; set; }
   public ItemClass ItemClass { get; set; }
   ...
   public MagicAttr[] Attributes { get; set; }
   ...
}

class ObjectEntity : ObjectModel {

   public int Class_id
   ...
   other foreign keys as primary keys
}

With this approach, my ObjectModel is still clean and independent of EntityFramework hell. I used Fluent API for configuration, (could also use Data anno. as well, the original ObjectModel is still clean)

Use ObjectEntity on your context i.e DbSet<ObjectEntity>

Beware!

You'll probably face with the issue of needing a downcast method (casting from parent to child). This is how I did implement a downcast on parent class (with using AutoMapper)

using AutoMapper;
...
    public ObjectEntity Downcast() {
        var config = new MapperConfiguration(cfg => cfg.CreateMap<ObjectModel, ObjectEntity>());
        var mapper = config.CreateMapper();
        return mapper.Map<ObjectEntity>(this);
    }

Would like to inform you aswell with:

Directly from MSDN:

https://msdn.microsoft.com/en-us/library/jj591620(v=vs.113).aspx#Anchor_7

Renaming a Foreign Key That Is Not Defined in the Model If you choose

If you choose not to define a foreign key on the CLR type, but want to specify what name it should have in the database, do the following:

modelBuilder.Entity<Course>() 
    .HasRequired(c => c.Department) 
    .WithMany(t => t.Courses) 
    .Map(m => m.MapKey("ChangedDepartmentID"));
Emirhan Özlen
  • 474
  • 2
  • 14
  • So basically what you're saying is that your domain classes are free of any ORM conventions (postfixing them with -`Model`). And then let your ORM classes (e.g. for EF postfix with -`Entity`) inherit from the base domain class (which then can adhere to any convention of that vendor)? I'd say that's a decent solution. Iyi bir fikir abi ;) – QuantumHive Jan 18 '18 at 13:57
1

After struggling with this same issue and lots of research on the net I ended up here as well and I ended up discarding this idea. However after working on another part of my project I discovered that this might actually be possible (at least it's working as expected for now). Using EF Core for this example though.

Having OP's class with key annotations removed as an example

public class FooBar
{
    public virtual Foo Foo { get; set; }

    public virtual Bar Bar { get; set; }

    //some more 'payload' properties
}

Assuming the Foo and Bar entity classes has navigation properties as below

public class Foo
{
    ...

    public virtual ICollection<FooBar> FooBars { get; set; }
}

public class Bar
{
    ...

    public virtual ICollection<FooBar> FooBars { get; set; }
}

I created this configuration class for FooBar

public class FooBarConfiguration : IEntityTypeConfiguration<FooBar>
{
    public void Configure(EntityTypeBuilder<FooBar> builder)
    {
        var fkFoo = "FooId";
        var fkBar = "BarId";

        builder.HasKey(fkFoo, fkBar);

        builder
            .HasOne(fb => fb.Foo)
            .WithMany(f => f.FooBars)
            .HasForeignKey(fkFoo);

        builder
            .HasOne(fb => fb.Bar)
            .WithMany(b => b.FooBars)
            .HasForeignKey(fkBar);
    }
}

And applied the configuration in the DbContext class OnModelCreating override

protected override void OnModelCreating(ModelBuilder builder)
{
    ....

    builder.ApplyConfiguration(new FooBarConfiguration());
}

As said, this seems to behave as expected. Hope it can help someone else searching for this same issue.

Claus Nielsen
  • 476
  • 6
  • 11