Entity Framework - entities with inherited properties [duplicate] - c#

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.

Related

Inheriting properties from model - Navigation properties can only participate in a single relationship

I'm trying to share common properties with multiple entities by using multiple levels of inheritance, but I'm running into an error.
Cannot create a relationship between 'User.SupersCreated' and 'Super.CreatedBy' because a relationship already exists between 'User.BasicsCreated' and 'Basic.CreatedBy'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'Super.CreatedBy' first in 'OnModelCreating'.
The structure of my models is as follows.
public class EntityBase
{
public Guid Id { get; set; }
public Guid CreatedById { get; set; }
public User CreatedBy { get; set; }
}
public class Basic: EntityBase
{
public string BasicProperty { get; set; }
}
public class Super : Basic
{
public string SuperProperty { get; set; }
}
public class User : IdentityUser<Guid>
{
public ICollection<Basic> BasicsCreated { get; set; }
public ICollection<Super> SupersCreated { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(x => x.BasicsCreated)
.WithOne(x => x.CreatedBy);
modelBuilder.Entity<User>()
.HasMany(x => x.SupersCreated)
.WithOne(x => x.CreatedBy);
}
The problem seems to be a result of Super Inheriting from Basic, or at least, the problem goes away when I remove this level of inheritance and make Super inherit from EntityBase (however than I'll lose the properties that exist in Basic).
Can anyone please help me understand why I'm getting this error and what should be done to fix it?
Edit
After considering this some more, I think I'm trying to abuse inheritance to do what it's not intended to do.
The database structure I was hoping to end up with, is:
Even though my Basic and Super tables share the same properties, with Super having it's own additional properties, there's no relationship between Basic data and Super data.
From having a look at Microsoft's tutorial on implementing inheritance, there's two options:
Table per type
Table per hierarchy
Neither of these are what I'm trying to achieve.
Perhaps I should be using interfaces to define the common properties that exist between unrelated entities. It seems like I need to back and re-evaluate my design anyway.
If some of the base classes of the entity is identified as entity (as with your Super and Basic), by default EF Core will try to use one of the database inheritance strategies.
If you don't want that (want to treat is just like non entity base class), then you have to configure that explicitly at the very beginning of the OnModelCreating, e.g. for your sample
modelBuilder.Entity<Super>().HasBaseType((Type)null);
or more generally using a loop similar to this
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
entityType.BaseType = null;
and then define explicitly the entity hierarchy if and where needed.

With EntityFramework, how can I make two databases with the same context but one without auto-increment IDs? [duplicate]

This question already has an answer here:
EF6 entity with DatabaseGeneratedOption.Identity Guid Id force insert my Id value
(1 answer)
Closed 5 years ago.
I'm using EntityFramework Code-First with a context that has a model like:
public class Person {
public int ID {get;set;}
public string Name {get;set;}
}
with a context like
public class DataContext: DbContext {
public DbSet<Person> People {get;set;}
}
And this gets saved into DatabaseA. When I insert a new person, it auto-generates an ID for that person. I need to have a DatabaseB that has the exact same structure, but without the ID auto-generation. I want to transfer some records from DatabaseA to DatabaseB but without DatabaseB generating new IDs. I know that I can wrap
[DatabaseGenerated(DatabaseGeneratedOption.None)]
around the ID property, but I only want it to apply to DatabaseB without affecting DatabaseA.
So the only solution I've come up with so far is to have a new model
public class PersonB : Person {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public new int ID { get; set; }
}
and a new context
public class DataContextB : DbContext {
public DbSet<PersonB> People {get;set;}
}
But this is a bit of a nuisance, as I'd have to maintain two sets of fairly identical models. Ideally, I'd like to be able to do something in
using(var db = new DataContextB) {
db.
}
You can override OnModelCreating function of DbContext and write some fluent api code.
Please have a look on this approach
public class DataContextB : DbContext
{
public DbSet<Person> People {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().Property(t => t.ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
base.OnModelCreating(modelBuilder);
}
}

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

Fluent mapping inheritance

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.

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