6

In this question: Ef Many To Many, an answer came up on how to manually specify a linking table. But I have a slightly unique situation (which I'm sure isn't really unique).

My two tables each have an Id field. E.G.: [dbo].[Account].[Id] and [dbo].[Person].[Id]. Each of these tables in my Code-First has the following OnModelCreating:

modelBuilder.Entity<Account>.HasKey(x => x.Id);
modelBuilder.Entity<Person>.HasKey(x => x.Id);

But my [dbo].[AccountsToPersons]... table has fields E.G.: [AccountId] and [PersonId]

The AccountsToPersons table is not represented by a class in code.

I obviously already have an existing Model, but we are using EF Code-First Fluent API instead of updating model from database.

So how do I change this code to make it work with mapping different ID columns names?

public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }
. . .
modelBuilder.Entity<Account>()
  .HasMany(a => a.Persons)
  .WithMany()
  .Map(x =>
  {
    x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
    x.MapRightKey("PersonId"); // <-- Person.Id  to AccountsToPersons.PersonId??
    x.ToTable("AccountsToPersons");
  });

When running a basic Linq To EF Query (from x in context.Accounts select x).ToList();, the query fails with the following error:

  • "Invalid Column Name 'Person_Id'."

But when running the Query (from x in context.Persons select x).ToList();, I get no error.

Other than basic typed columns, my models have these added to them:

// In my Account Model, I also have this property:
public IList<Person> Persons { get; set; }

// In my Person Model, I also have this property:
public IList<Account> Accounts { get; set; } // <-- in the Person model

And please note that even though my Accounts query passes and has field information, the Persons field is always null, even though I'm sure there are links in my AccountsToPersons table.

Community
  • 1
  • 1
Suamere
  • 5,691
  • 2
  • 44
  • 58
  • 1
    This looks like standard EF naming convention. Have you tried this? Does it work? What does `Account` and `Person` look like? – Jasen Nov 21 '14 at 19:02
  • @Jasen I thought it was working at first, though my test was only in creating the model. I ran my first LINQ and it failed with the added information above. – Suamere Nov 22 '14 at 05:29
  • This fluent mapping looks correct if your existing join table has columns _AccountId_ and _PersonId_. The error lies elsewhere. – Jasen Nov 24 '14 at 18:34
  • @Jasen The error shows `Account_Id` as the invalid column name (with an underscore as shown). I don't specify that anywhere at all in my Database or Code. So it's obviously a generated name that EF is expecting to find. I only assume it's trying to find it within the Left and Right tables since I obviously specify the mapping of the joining table. It's possible that the Fluent API for those individual tables, or for the model in general, needs something to say "don't auto generate your idea of column names." I dunno. But this is the result. – Suamere Nov 24 '14 at 19:36
  • @Suamere yeah, thats a default behaviour from EF to generate the fk keys by [table_name]_[table_pk]. still kinda strange that is doing so even though you specified a mapping – alexo Nov 24 '14 at 19:54
  • do you get that error when you do a query for the `Person` Entity or for the `Account` Entity? or both? – Claies Nov 24 '14 at 19:56
  • @AndrewCounts Thanks for the great question. I added information. – Suamere Nov 24 '14 at 20:38
  • Wouldn't this be more suitable for Database Admins? http://dba.stackexchange.com/ – John Demetriou Nov 24 '14 at 20:47

3 Answers3

4

Try adding p => p.Accounts to your WithMany clause:

modelBuilder.Entity<Account>()
  .HasMany(a => a.Persons)
  .WithMany(p => p.Accounts) // <-- I think this should fix it
  .Map(x =>
  {
    x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
    x.MapRightKey("PersonId"); // <-- Person.Id  to AccountsToPersons.PersonId??
    x.ToTable("AccountsToPersons");
  });
SOfanatic
  • 5,523
  • 5
  • 36
  • 57
  • >.< oh em gee you are correct. Such an obvious thing that just needs somebody to step back away from the code to see. Thank you, sir. Also... that's what you get for copy/pasting code, lol. The comments in the linked question's answer even say that some instances require an empty WithMany, and some uses require the lambda. But I'm glad I have this question here for people getting the same error to get more clarification. Thanks again! – Suamere Nov 24 '14 at 21:16
  • @Suamere check out this post: http://stackoverflow.com/questions/5927272/ef-code-first-withmany – SOfanatic Nov 24 '14 at 22:18
  • @SOfanatic yeah that makes total sense, i`ll probably have a look later today again over it. thanks – alexo Nov 25 '14 at 05:52
2

I just built up a test solution for your problem and for me it looks that is working.

One thing that i see you did different than me is:

public IList<Person> Persons { get; set; } // <-- in the Account model
public IList<Account> Accounts { get; set; } // <-- in the Person model

Try modifying into this:

public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }

If this doesn't work i`ll post my entire setup.

My structure looks like this and it works for the queries you displayed:

public class Person
{
    public int ID { get; set; }

    public string Name { get; set; }

    public IList<Account> Accounts { get; set; }
}

public class Account
{
    public int ID { get; set; }

    public string Name { get; set; }

    public IList<Person> Persons { get; set; }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Account> AccountSet { get; set; }

    public DbSet<Person> PersonSet { get; set; }

    public ApplicationDbContext()
        : base("DefaultConnection")
    {
        this.Database.Log = (msg) => { Debug.Write(msg); };
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<Account>().HasKey(x => x.ID);
        modelBuilder.Entity<Person>().HasKey(x => x.ID);

        modelBuilder.Entity<Account>()
            .HasMany(a => a.Persons)
            .WithMany()
            .Map(w =>
            {
                w.MapLeftKey("AccountId");
                w.MapRightKey("PersonId");
                w.ToTable("AccountsToPersons");
            });
    }
}
alexo
  • 936
  • 8
  • 16
  • Thank you for taking the time to replicate my model. However, we have a persistent-ignorant model project, and adding Entity Framework for the DbSet is off the table. I'm going to take a few minutes to see if it works anyway, but I hope there is an answer that wouldn't force us to break our principles as we currently have set up. – Suamere Nov 24 '14 at 20:54
  • My Entity Framework won't allow me to use DbSet. When researching why, I found [This Question](http://stackoverflow.com/questions/14351690/ef5-code-first-error-cannot-be-inferred-from-the-usage) stating EF needs access to the ICollection interface's Add() functionality in order to work with the datasets. But more information from you may be in order. I could be missing something – Suamere Nov 24 '14 at 21:02
  • what could be the reason for not supporting the DbSet ? And yes, the ICollection is for navigation properties, those are also marked as partial because EF is building a new class on top of your and is overrinding everything that it seems as `virtual`, in this case the ICollection properties – alexo Nov 24 '14 at 21:04
  • OOO, I think you're confusing my personal models with the DbSet models in my Code-First... maybe I should put something specific in the question. In my CONTEXT, I am CERTAINLY using DbSet. But in my personal models that represent the tables, each individual model has a property that represents a collection of its Many-To-Many relationship. And those properties are of type `IList`. I think that's where the confusion is. – Suamere Nov 24 '14 at 21:06
  • hmmm, well from what i see you posted that you ran the query `(from x in context.Accounts select x).ToList();` and here I suppose that `context` is the `DbContext` which would be your application`s db context. and if this is the case then `Acounts` must be of type `DbSet`. maybe a little more info would help, so i can see exactly the structure you got and the usage your trying to get going – alexo Nov 24 '14 at 21:10
0

Have you tried modifying your AccountsToPersons mapping slightly:

 modelBuilder.Entity<Account>()
   .HasMany(a => a.Persons)
   .WithMany(p => p.Accounts) <-- Change
   .Map(x =>
    {
         x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
         x.MapRightKey("PersonId"); // <-- Person.Id  to AccountsToPersons.PersonId??
         x.ToTable("AccountsToPersons");
    });
mreyeros
  • 4,359
  • 20
  • 24