2

I've been at this for a while, and I really cannot figure it out. I'm using EF5, Mvc 4, table-per-type inheritance.

I have classes Tenant and Landlord that inherit from UserProfile. When registering a user selects what account type they want, and based on this selection the details they enter on the form (username, first name, last name, email, account type, password) should be added to UserProfile table. I then want the PK of that table to be added as a foreign key to table that matches the account type they selected, either Tenant or Landlord.

I was trying to achieve the above using this method, but that resulted in the problems described in that question.

I've now created a RegisterTenantModel class, with properties that map to a Tenant, and I'm passing an object of type RegisterTenantModel to the AccountController/Register like so...

// ...
if (tenantModel.AccountType == AccountType.Tenant)
                {
                    using (var db = new LetLordContext())
                    {
                        var t = db.UserProfile.Create<Tenant>();

                        WebSecurity.CreateUserAndAccount(tenantModel.UserName, tenantModel.Password,
                             t = new Tenant
                            {
                               AccountType = tenantModel.AccountType,
                               DateWantToRentFrom = DateTime.Now, 
                               DateWantToRentTo = DateTime.Now, 
                               DesiredPropertyLocation = "",
                               Email = tenantModel.Email, 
                               FirstName = tenantModel.FirstName,
                               UserId = WebSecurity.CurrentUserId, 
                               UserName = WebSecurity.CurrentUserName

                               // More properties that map to Tenant entity
                               // ...
                            },
                            false);

                        db.SaveChanges();

...but now I'm getting an error Invalid column name for each of the column names inside t= new Tenant.

Has anyone ever had to do something similar? Am I approaching this the right way? Help would be much appreciated.

EDIT

Based on what Antonio Simunovic posted below, I think I've come to realise what the problem is. My WebSecurity.InitializeDatabaseConnection() goes like this...

WebSecurity.InitializeDatabaseConnection("LetLordContext", "UserProfile", 
"UserId", "UserName", autoCreateTables: true);

...so when I call Websecurity.CreatUserAndAccount() in AccountController/Register it's writing to the UserProfile table as per the parameters in WebSecurity.InitializeDatabaseConnection(). Looking at the question linked above, you'll see that, based on the account type selected on the form, either a Tenant or Landlord will be added to the UserProfile table by calling...

var tenant = db.UserProfile.Create<Tenant>();
//...
db.UserProfile.Add(tenant);
db.SaveChanges();

...resulting in duplicate entries being added to the UserProfile table.

I think the solution is to create a new table, and point WebSecurity.InitializeDatabaseConnection() at it.

Community
  • 1
  • 1
MattSull
  • 5,514
  • 5
  • 46
  • 68
  • I think you are on the right track with your edit. In all my implementations of Membership I have configured it to use my user tables and not the default Membership / other tables specific for the membership framework. I don't know that this is best practice, but when not using it just out of the box it is the way I have always seen it done. (Also not 100% sure this comment is relevant). – Matthew Jan 31 '13 at 16:06
  • Just tried it and it works, might not be pretty but table-per-type inheritance is working along with Membership. Thanks for the help. – MattSull Jan 31 '13 at 16:56

4 Answers4

2

What does your WebSecurity.InitializeDatabaseConnection() method call looks like? That call identifies database table to store user data.

WebSecurity.CreateUserAndAccount() method does not use context to store supplied data, it simply maps object property names of third call argument to columns in table defined in InitializeDatabaseFile() method.

If you're not familiar with SimpleMembership mechanics, take a look at this article: http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx

Antonio Simunovic
  • 229
  • 1
  • 2
  • 10
  • The method is like this WebSecurity.InitializeDatabaseConnection("LetLordContext", "UserProfile", "UserId", "UserName", autoCreateTables: true); If it does not use a context to store data, is there any to achieve what I want? – MattSull Jan 30 '13 at 23:41
1

I saw exactly the same symptoms, and it was because I hadn't declared the base class as a DbSet<BaseClassName> on the DbContext.

(Counterintuitive, and I never need to refer to the collection of base class entities in my application, but there you go.)

andrewf
  • 1,350
  • 1
  • 13
  • 19
0

I have seen this error when I forget to annotate the subclass with the table name in TPT.

[Table("Tenant")]
public class Tenant : UserProfile { ...

Could it be as simple as this?

EDIT

A quick search also recommends pairing down your fields in the Entity and adding them back in one at a time to see if a single field is to blame (and it will provide an error message that indicates many failed columns). This sounds a little suspect to me, but might be worth a try:

Entity Framework 5 Invalid Column Name error

Community
  • 1
  • 1
Matthew
  • 9,851
  • 4
  • 46
  • 77
  • Unfortunately not, Tenant class is as above. There must be a way to do it? Surely they wouldn't ship Mvc 4 with a UserProfile model - that relies on websecurity - that couldn't be extended? – MattSull Jan 30 '13 at 16:58
  • 1
    Added an update on something else I found although haven't seen this myself. – Matthew Jan 30 '13 at 16:59
  • I came across that question, however when I get the error page in my browser, it list all the fields as invalid. I've also debugged and the correct data is being passed to the CreateUserAndAccount method, it just throws the exception. – MattSull Jan 30 '13 at 17:10
  • Note he was getting all fields as invalid as well even though only one was the issue. That's what sounds suspect, but might be worth a try. I would also start with removing the DateTime stuff. I am not advocating this as a good idea, just an idea :) – Matthew Jan 30 '13 at 17:35
  • The question you linked doesn't seem to be related. This problem appears to be that EF is trying to use TPC inheritance instead of TPT. – Sam Jan 17 '14 at 05:27
  • There's a spelling mistake in the answer which could be misleading: should say 'paring down your fields' (i.e., reducing or removing them), not 'pairing' them! – andrewf Mar 25 '15 at 17:06
0

Solution

  1. Ensure that all derived classes are explicitly mapped to tables. Two simply ways to do this are:
    1. TableAttribute class attribute: [Table("Tenant")]
    2. ToTable Fluent API method: ToTable("Tenant")
  2. Ensure that the base type is abstract.
  3. Ensure that you are not calling the MapInheritedProperties configuration method for any tables. That method implies TPC.
  4. Ensure that the base type is registered as part of the model. Two simple ways to do this are:

    1. Add an accessor property to your subclass of DbContext:

      public DbSet<UserProfile> Users { get; set; }
      
    2. Override OnModelCreating in your subclass of DbContext and call DbModelBuilder.Entity:

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
          base.OnModelCreating(modelBuilder);
          modelBuilder.Entity<UserProfile>();
      }
      

Explanation

As the error message indicates, Entity Framework appears to be looking for the "base" table's columns in the "derived" table. This indicates that it's trying to use "Table per Concrete Type" (TPC) inheritance rather than "Table per Type" (TPT).

Making Entity Framework use TPT is quite fiddly. If you very carefully study the example implementation at Inheritance with EF Code First: Part 2 – Table per Type (TPT), you might spot what you're missing. The follow-up article, Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC), summarises the required configuration as follows:

As you have seen so far, we used its Requires method to customize TPH. We also used its ToTable method to create a TPT and now we are using its MapInheritedProperties along with ToTable method to create our TPC mapping.

My experimentation has shown that the above summary is oversimplified. It appears that a single minor seemingly-unrelated configuration difference can cause an unexpected inheritance strategy to be used.

Sam
  • 40,644
  • 36
  • 176
  • 219