I've been going around in circles with this and don't seem to be able to google the right answers - even after hours spent on this, so you're my last resort!
Setup
In my web app I would like to enable users to use different authentication mechanisms to access their accounts. In addition to the usual user/password thing I'd like to enable them to use Google's OpenId, Yahoo's OpenId, or even Facebook. This seems rather straightforward to map into classes: an abstract Account
with several <something>Account
classes inheriting some basic properties from Account
. I started with the following two classes:
public abstract class Account
{
public int Id { get; set; }
public int OwnerId { get; set; }
public virtual Person Owner { get; set; }
}
public class OpenIdAccount : Account
{
public string Identifier { get; set; }
}
Being a bit of a perfectionist, and doing a lot of db dev in my day job, I decided table per type (TPT) would be the most desirable option. As EF4 uses TPH by default, on the DbContext side of things I defined:
public class MySampleDb : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<OpenIdAccount> OpenIdAccounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().MapHierarchy(a => new
{
a.Id,
a.OwnerId
}).ToTable("Account");
modelBuilder.Entity<OpenIdAccount>().MapHierarchy(oid => new
{
oid.Id,
oid.Identifier
}).ToTable("OpenIdAccount");
}
}
Now, I wished to start with some simple tests, and thought that seeding the database on each run was a good way to have consistent data to work with:
protected override void Seed(MySampleDb context)
{
Person johns = new Person
{
Id = 1,
Nickname = "John Skeet"
};
OpenIdAccount google = new OpenIdAccount
{
Id = 2,
OwnerId = 1,
Identifier = "https://www.google.com/accounts/o8/id?id=AItOawnmUz4e6QIn9mgd98WMAbnzC25sji5lpSM"
};
context.People.Add(johns);
context.Accounts.Add(google);
context.SaveChanges();
}
(Not sure why but DbContext didn't seem to ever try populate my db with the above data until I exlicitly called .SaveChanges()
... any ideas?)
The Problems
One
In the database, EF didn't define any relationships between Account
and OpenIdAccount
. This was the first warning sign something was not right; surely OpenIdAccount should have its Id
defined as both a PK and a FK pointing at Account.Id?
Two
I get the following UpdateException
when .net tries to execute .SaveChanges()
:
A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.
Next steps
Today is my first day with EF4 and code first development. Having spent numerous hours reading about EF4 + code-first + custom mapping I came to a point where I'm permanently stuck and need a kick in the right direction before I can get going again :-)
So I'm hoping that you guys can make sense of the above and explain some silly mistake / misunderstanding on my part!