Fluent mapping inheritance - c#

I have an issue I don't know the best solution for. Hopefully someone here can help =)
What I'm trying to solve:
We have to type of users in a system, person and organization.
I want to have a shared login table for the two (ie the user probably won't know which type of user they are, they just relate to username and password).
So I've created a login table for usernames and passwords. But I need to know who the login is connected to, so I need a reference to either person or organization.
Consider the following classes (simplified):
public class Login
{
public string Username {get; set;}
public string Password {get;set;}
}
public class LoginPerson : Login
{
public Person Person {get;set;}
}
public class LoginOrg : Login
{
public Organization Organization {get;set;}
}
public class Person
{
public LoginPerson LoginPerson {get;set;}
//Lots of other properties. Removed for simplicity
}
public class Organization
{
public LoginOrg LoginOrg {get;set;}
//Lots of other properties. Removed for simplicity
}
The person configuration is set up as follows:
public class PersonConfiguration : EntityTypeConfiguration<Person>
{
public PersonConfiguration()
{
HasRequired(p => p.LoginPerson).WithRequiredPrincipal(p => p.Person);
}
}
First of all, this doesn't work. I get an exception saying
"System.Data.EntityCommandCompilationException: System.Data.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.."
So my first question is why doesn't this work?
My second question is: which strategy is best suited for this kind of inheritance? TPT, TPH or TPC?

Well, for starters, none of your entities have keys. You need a primary key to make them work. EF uses a convention to do this, which is the class name plus Id at the end, like PersonId, or you key be explicit with an attribute of [Key]
Second, your model is confusing and fairly circular. And without primary keys, there's no way to to create associations.
I'm confused about why you have a member that is a LoginPerson in a Person object, and the same for an Organization? In any event, you really need to rethink this model and figure out what your keys are.

The solution to my exception was to set up the correct configuration ;-)
PersonConfiguration didn't need to include any configuration for the LoginPerson property. I added a LoginPersonConfiguration ->
public class LoginPersonConfiguration : EntityTypeConfiguration<LoginPerson>
{
public LoginPersonConfiguration()
{
ToTable("LoginPerson");
HasKey(l => l.Id);
HasRequired(l => l.Person).WithOptional(p => p.LoginPerson).Map(t => t.MapKey("PersonId"));
}
}
And I also had to add the Login to the DbContext class
public class MyDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Login> Logins { get; set; }
}
When it comes to which strategy is best, I have decided to go for TPT.

Related

ASP.NET MVC 4 Code-First IdentityUserRole with additional primary key 3-way-relation

I have been searching the web for weeks in hope of finding a solution to an issue I'm having when using the .NET Identity framework to create my database.
One of the closest related issues i found is this: Customised IdentityUserRole primary key
However I am using automatic migrations, in order to avoid manually specifying Up and Down methods.
What I'm trying to achieve is to have an additional primary key in my UserRoles table, which is derived from IdentityUserRoles and the implementation looks like this:
public class UserRole : IdentityUserRole
{
[Key, ForeignKey("Company")]
public string CompanyId { get; set; }
public Company Company { get; set; }
}
My OnModelCreating method looks like this:
...
public DbSet<UserRole> UserRolesExt { get; set; }
...
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
// Failed attempt to make property a primary key below:
modelBuilder.Entity<UserRole>().HasKey(ur => ur.CompanyId).ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
}
However, the table ends up looking like this:
http://i.imgur.com/n0dne7O.png
One thing that got me puzzled is the fact that it is also set as a nullable type, but I have a feeling that is refering to my custom Company class which only contains primitive data types.
The reason why I want this 3-way-relationship for this entity is that my system will contain several users in several companies, where each user can be a member of more than one company, and hereby also individual roles of that user within the given company.
I would if possible, very much like to avoid making my own UserStores and RoleStores etc., which is some of the solutions I've found, since I would like to keep it simple so to say.
An example from one of my classes that does not derive from Identity classes looks as follows and works just as intended:
public class UserModule
{
[Key, ForeignKey("User"), Column(Order = 0)]
public string UserId { get; set; }
public User User { get; set; }
[Key, ForeignKey("Module"), Column(Order = 1)]
public string ModuleId { get; set; }
public Module Module { get; set; }
}
I have tried fiddling around with various versions of my data annotations in my UserRole class but without any luck. Is there really no way of extending this UserRole class to have an additional primary key, without having to make major customizations?
It should also be noted that if I simply edit the database manually after creation, I am able to make all three properties primary keys and the table works as intended. But I would very much like to know how I can make it work properly so to say.
Any help would be greatly appreciated!
If I understand correctly, you want to add an additional column to the IdentityUserRole class and have that column be part of the primary key. To do so, you have to slightly modify your OnModelCreating method as follows:
modelBuilder.Entity<UserRole>()
.ToTable("UserRoles")
.HasKey(r => new { r.UserId, r.RoleId, r.CompanyId })
.HasRequired(r => r.Company).WithMany();
This code essentially redefines the primary key to also include the CompanyId column and then makes it a required field.
You should also remove the [Key, ForeignKey("Company")] attributes from the CompanyId property, to prevent the annotations from interfering with the fluent syntax. I don't think the modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles"); line is necessary too, you could try removing it.

IdentityDbContext User DbSet Name

I've created a custom user inheriting from IdentityUser called Contacts, and my applications dbcontext inherits from IdentityDbContext like so:
public class Contact : IdentityUser<int, ContactLogin, ContactRole, ContactClaim>
{
public Contact()
{
}
}
public class dbcontext : IdentityDbContext<Contact, Role, int, ContactLogin, ContactRole, ContactClaim>
{
public dbcontext()
: base("dbcontext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// IdentityDbContext base - must be called prior to changing identity configuration
base.OnModelCreating(modelBuilder);
// custom identity table names and primary key column Id names
modelBuilder.Entity<Contact>().ToTable("Contacts").Property(p => p.Id).HasColumnName("ContactId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<ContactRole>().ToTable("ContactRoles");
modelBuilder.Entity<ContactLogin>().ToTable("ContactLogins");
modelBuilder.Entity<ContactClaim>().ToTable("ContactClaims").Property(p => p.Id).HasColumnName("ContactClaimId");
modelBuilder.Entity<Role>().ToTable("Roles").Property(p => p.Id).HasColumnName("RoleId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
By default IdentityDbContext contains a Users DbSet. Is it possible to change the name of this DbSet to match the type that it's implementing, e.g Contacts?
It's not a big deal, but it would just be nice to refer to the DbSet using dbcontext.Contacts instead of dbcontext.Users.
Thanks.
The base IdentityDbContext uses: public virtual IDbSet<TUser> Users { get; set; } to expose the Users DbSet.
You'll need a similar property for your own implementation, e.g: public IDbSet<Contacts> Contacts { get; set; }
Update
Question was regarding renaming the existing DbSet of Contacts from Users to Contacts.
No, you can't do this out of the box. You could attempt to wrap it and expose it again, but this isn't really the right thing to do. See this question for an in depth discussion.
Just a note that if you decide to overwrite anything or add your own, the default EF implementation of UserStore will use the DbSet named Users. Just something to keep an eye on if you get unexpected behavior.
Generally what I tend to do is have a big separation of concerns right.
So I have:
public IDbSet<User> Users { get; set; }
This represents anyone who wants to log into my system. So now I want to model actual concepts into my database, concepts that relate to real world things. So I have a system administrator for example, I will create an entity for this.
public class SystemAdministrator
{
public string Name { get; set; }
public int LocationId { get; set; } // a complex representation of where this administrator works from
public int UserId { get; set; } // this is now a reference to their log in
}
Now my context will look like this:
public IDbSet<User> Users { get; set; }
public DbSet<SystemAdministrator> SystemAdministrators { get; set; } // I use DbSet because it exposes more methods to use like AddRange.
This means now my database has proper representations of real world concepts which is easy for everyone to develop against. I do the same for Clients or Employees.
This also means that I can move away from primitive obsession

Entity Framework, strange behaviour with required reference, lazy loading

This goes for both Entity Framework 4 (4.3.1) and 5.
I have a User class (to go with my Entity Framework MembershipProvider). I've removed some of the properties to simplify. The actual User is from the MVCBootstrap project, so it's not part of the same assembly as the other classes.
public class User {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
[StringLength(256)]
public String Username { get; set; }
}
And then I have this class:
public class NewsItem {
public Int32 Id { get; set; }
[Required]
[StringLength(100)]
public String Headline { get; set; }
[Required]
public virtual User Author { get; set; }
[Required]
public virtual User LastEditor { get; set; }
}
Then I create the database context (The DbSet for the user is in the MembershipDbContext):
public class MyContext : MVCBootstrap.EntityFramework.MembershipDbContext {
public MyContext(String connectString) : base(connectString) { }
public DbSet<NewsItem> NewsItems { get; set; }
}
Running this code will give me this exception when the database is being created:
Introducing FOREIGN KEY constraint 'FK_dbo.WebShop_dbo.User_LastEditor_Id' on table 'WebShop' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
So I change the database context:
public class MyContext : MVCBootstrap.EntityFramework.MembershipDbContext {
public MyContext(String connectString) : base(connectString) { }
public DbSet<NewsItem> NewsItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new NewsItemConfiguration());
}
}
And this configuration:
public class NewsItemConfiguration : EntityTypeConfiguration<NewsItem> {
public NewsItemConfiguration() {
HasRequired(n => n.Author).WithOptional();
HasRequired(n => n.LastEditor).WithOptional();
}
}
Or is this wrong?
Anyway, when I run the code, the database get's created, and the database seems okay (looking at foreign key constraints etc.).
But, then I get the 10 latest NewsItems from the context, and start loading them into view models, part of this is accessing the Author property on the NewsItem. The controller doing this takes forever to load, and fails after a long, long time. When running in debug mode, I get an exception in this piece of code: this.AuthorId = newsItem.Author.Id;, then exception I get is this:
A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
It's probably something simple and stupid I'm doing wrong, I'm sure I've get similar code running on several sites, so .. what is causing this? Are my models wrong, is it the database context, or?
This part
Introducing FOREIGN KEY constraint 'FK_dbo.WebShop_dbo.User_LastEditor_Id' on table 'WebShop' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.
is actually a SQL Server issue (and an issue of many other RDBMS's). It is a complex issue resolving multiple cascade paths, and SQL Server decides just to punt and not try. See
Foreign key constraint may cause cycles or multiple cascade paths?
You were trying to configure your model to delete the child Author and LastEditor objects when the NewsItem is deleted. SQL Server won't do that.
Come to think of it... is that what you want? It seems you would want to disassociate the Author and LastEditor from the NewsItem, not delete them from the database.
Your object model requires a 1:1 relationship between NewsItem and Author, and between NewsItem and LastEditor. I'm not sure what this refers to in the code
this.AuthorId = newsItem.Author.Id;
but it seems to me, you should be making the assignment the other way around, e.g.
newsItem.Author = myAuthorInstance;
or if you include foreign key properties in your model and if you have previously saved your author instance and have an Id:
newsItem.AuthorId = myAuthorInstance.Id;
If you share the generated DB schema (relevant parts) that would make it easier to diagnose the issue.
User can be an author of several news items. Also, user can be editor of several news items.
Hence, relationship have to be "one-to-many":
public class NewsItemConfiguration : EntityTypeConfiguration<NewsItem> {
public NewsItemConfiguration() {
HasRequired(n => n.Author).WithMany();
HasRequired(n => n.LastEditor).WithMany();
}
}

Entity Framework - entities with inherited properties [duplicate]

This question already has an answer here:
How to map inherited entities in EF code-first
(1 answer)
Closed 2 years ago.
I searched the threads on here and found multiple similar posts but no solutions
Assume I have a User table in my db that I've mapped to a simple User entity
public class User{
public int UserId {get;set;}
public string Username {get;set;}
}
I want to create a new class that will encapsulate an ExternalUser which has all the same fields as User but adds a few more fields. The fields for my ExternalUser will be populated from a view in the db that pulls in both the fields from User and the additional fields required for ExternalUser
public class ExternalUser : User{
public int SomeExternalId{get;set;};
public string SomeExternalProp{get;set;};
}
but no matter how I seem to define my mappings for this new object I get the following error:
The property 'UserId' is not a declared property on type 'ExternalUser'. Verify that the property has not been explicitly excluded from the model by using the Ignore method or NotMappedAttribute data annotation. Make sure that it is a valid primitive property.
Can someone share the correct way to map this. Its stuff like this that makes me hate EF, simply inheriting a POCO shouldn't cause it to blow up, especially not when all the fields exist in the underlying view that I'm pointing to. Much thanks!
I am not sure about your use case but if you have the option to make User an abstract base class, then you can use the Table per Concrete Type approach.
You would need to make User an abstract class and call MapInheritedProperties() when creating the model mappings for ExternalUser:
public abstract class User
{
public int UserId { get; set; }
public string Username { get; set; }
}
[Table("ExternalUser")]
public class ExternalUser : User
{
public int SomeExternalId { get; set; }
public string SomeExternalProp { get; set; }
}
Note that I am using [Table] attribute to map the entity name to table name (you can also do this in OnModelCreating method but I find it cleaner to use the [Table] attribute):
And this is OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<ApplicationDbContext>(new CreateDatabaseIfNotExists<ApplicationDbContext>());
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// I believe the inherited properties are mapped by default
modelBuilder.Entity<ExternalUser>().Map(m =>
{
m.MapInheritedProperties();
});
}
Note that I am removing PluralizingTableNameConvention as I don't want plural table names.

NHibernate, simple question, extra association

I've got this question that's been bugging me and my vast and unfathomable intellect just can't grasp it. The case: I want to make multiple one-to-many relationships to the same entity using the fluent nhibernate automapper and export schema.
I have:
Base Class:
public abstract class Post<T> : Entity, IVotable, IHierarchy<T>
{
public virtual string Name
{
get; set;
}
public virtual string BodyText
{
get; set;
}
public virtual Member Author
{
get; set;
}
}
and Inheriting Class:
[Serializable]
public class WallPost : Post<WallPost>
{
public virtual Member Receiver
{
get; set;
}
}
The 'Member' properties of WallPost is a foreign key relationship to this class:
public class Member : Entity
{
public Member()
{
WallPosts = new IList<WallPost>();
}
public virtual IList<WallPost> WallPosts
{
get; set;
}
}
I hope you're with me until now. When I run the exportschema of nhibernate I expect to get a table wallpost with 'author_id' and 'receiver_id' BUT I get author_id, receiver_id,member_id. Why did the Nhibernate framework add member_id, if it's for the collection of posts (IList) then how do you specify that the foregin key relationship it should use to populate is receiver, i.e. member.WallPosts will return all the wallposts of the receiver.
I hope i made sense, if you need anything else to answer the question let me know and I'll try to provide.
Thanks in advance
P.s. If I change the property name from 'Receiver' to 'Member' i.e. public virtual Member Member, only two associations are made instead of 3, author_id and member_id.
E
THE SOLUTION TO THIS QUESTION FOR ANYONE ELSE WONDERING IS TO ADD AN OVERRIDE:
mapping.HasMany(x => x.WallPosts).KeyColumn("Receiver_id");
Most likely (I don't use auto mapping, I perfer to write my own classmaps) it's because the auto mapping assuming that your Member's "WallPosts" is controled by the wall posts. So it creates a member_id for that relationship.
Edit: Try adding an override in your fluent config, after AutoMap add:
.Override<Member>(map =>
{
map.HasMany(x => x.WallPosts, "receiver_id")
.Inverse;
});

Categories