By code-first approach (but with an existing db schema), we are trying to map 2 different entities (Customer and Resource) to the same table. Both entities has the same keys and mapping.
However, when running the app, we have a runtime error telling us that mysterious message:
System.InvalidOperationException: Type 'Resource' cannot be mapped to table 'CLIENT' since type 'Customer' also maps to the same table and their primary key names don't match. Change either of the primary key property names so that they match.
Example:
public class EntityA
{
public string ID { get; set; }
public string Discriminator { get; set; }
public string TimeStamp { get; set; }
}
public class EntityB
{
public string ID { get; set; }
public string Discriminator { get; set; }
public string CreatedBy { get; set; }
}
public class EntityAConfiguration : EntityTypeConfiguration<EntityA>
{
public EntityAConfiguration()
{
HasKey(x => new {x.ID, x.Discriminator } );
Property(x => x.ID).HasColumnName("MyTable_ID").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.Discriminator).HasColumnName("MyTable_Discriminator").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.TimeStamp).HasColumnName("MyTable_TimeStamp");
ToTable("MyTable");
}
}
public class EntityBConfiguration : EntityTypeConfiguration<EntityB>
{
public EntityBConfiguration()
{
HasKey(x => new { x.ID, x.Discriminator });
Property(x => x.ID).HasColumnName("MyTable_ID").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.Discriminator).HasColumnName("MyTable_Discriminator").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.CreatedBy).HasColumnName("MyTable_CreatedBy");
ToTable("MyTable");
}
}
The above code is similar to our Customer/Resource code (but simpler for the explanation!).
However, get get the same Error message, telling us that EntityA and EntityB cannot be mapped to the same table because their primary key names don't match.
Any idea of what is wrong with our mapping?
Any idea how we could different entities to the same table?
Thanks for your help
Mapping 2 entity to one table requires that you create a Complex Type or Table Per Hierarchy (TPH). You can't just map 2 entities to one table like this. Let me know which one is better describe your domain model and I will provide you with the required object model/fluent API code.
Update: TPH Mapping:
public abstract class EntityBase
{
[Column(Name = "MyTable_ID")]
public string ID { get; set; }
[Column(Name = "MyTable_Discriminator")]
public string Discriminator { get; set; }
}
public class EntityA : EntityBase
{
[Column(Name = "MyTable_TimeStamp")]
public string TimeStamp { get; set; }
}
public class EntityB : EntityBase
{
[Column(Name = "MyTable_CreatedBy")]
public string CreatedBy { get; set; }
}
public class StackoverflowTestContext : DbContext
{
public DbSet<EntityBase> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityBase>()
.HasKey(x => new { x.ID, x.Discriminator });
modelBuilder.Entity<EntityBase>()
.Map<EntityA>(m => m.Requires("TPHDiscriminator")
.HasValue("yourDesiredValueForA"))
.Map<EntityB>(m => m.Requires("TPHDiscriminator")
.HasValue("yourDesiredValueForB"))
.ToTable("MyTable");
}
}
Related
I'm learning EF Core and the below is my three POCOs:
public class County
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class City
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class People
{
[Key]
public int pid { get; set; }
public string pname { get; set; }
public int cid { get; set; }
public City WhichCity { get; set; }
}
I'm expecting two foreign keys but only got one from City table. How to make it(using annotation or fluent API or whatever) except explicitly define a County variable to People class.
Just want to clarify: you don't need to have navigation properties, i.e., public City City { get; set; } in order to setup relationships. The only things you need are the foreign key and proper configurations.
I think the following configuration would work for you (not tested though):
Entities
Here I also purposely modified your existing classes to follow C# Naming Conventions, if you care. Remember, if you're doing Code First, that means you can have your classes however you want first. You think about persistence later on. Actually I will show you how you can rename classes' properties when you persist them to your database via Configurations.
public class County
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
public class People
{
public int Id { get; set; }
public string Name { get; set; }
public int CityId { get; set; }
// Optional
//public City City { get; set; }
public int CountyId { get; set; }
// Optional
//public County County { get; set; }
}
Configurations
Instead of using Data Annotation, you can use Fluent API with configurations to configure how you want to map your classes back to database.
public class CountyConfiguration : IEntityTypeConfiguration<County>
{
public void Configure(EntityTypeBuilder<County> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_county"); // If you want to rename the table
}
}
public class CityConfiguration : IEntityTypeConfiguration<City>
{
public void Configure(EntityTypeBuilder<City> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_city"); // If you want to rename the table
}
}
public class PeopleConfiguration : IEntityTypeConfiguration<People>
{
public void Configure(EntityTypeBuilder<People> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("pid"); // If you want to rename to "pid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("pname"); // If you want to rename to "pname"
// Relationship
builder.HasOne<County>() // People has one County
.WithMany() // County has many people
.HasForeignKey<County>(x => x.CountyId); // Foreign key is CountyId
builder.HasOne<City>() // People has one City
.WithMany() // City has many people
.HasForeignKey<City>(x => x.CityId); // Foreign key is CityId
builder.ToTable("so_people"); // If you want to rename the table
}
}
And lastly, you need to apply those configurations OnModelCreating:
public class YourDbContext : DbContext
{
public DbSet<County> Counties { get; set; }
public DbSet<City> Cities { get; set; }
public DbSet<People> People { get; set; }
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new CountyConfiguration());
builder.ApplyConfiguration(new CityConfiguration());
builder.ApplyConfiguration(new PeopleConfiguration());
}
}
DISCLAIM: wrote it by hand. Not tested.
So I'm trying to use the MVC 4 internet application template and the UserProfile database tables it creates for accounts and then add tables that have dependencies on the UserProfile table for additional information.
The model would be UserProfile 0 ---> 1 UserType1 and UserProfile 0 ----> UserType2
where the userprofile table may have a dependent record in UserType1 and may have a dependent record in UserType2 and if there is an entry in either UserType1 or UserType2 its primary key is a foreign key that is the UserId from User Profiles
The POCO is:
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public int type { get; set; }
public virtual UserType1 UserType1 { get; set; }
public virtual UserType2 UserType2 { get; set; }
public class UserType1
{
[key,ForeignKey("UserProfile")]
public virtual int UserId {get;set;}
public int myval {get;set;}
public UserProfile UserProfile {get; set;}
}
public class UserType2 //same as usertype 1
I've tried adding model mapping statements but to no avail
Model mapping data for user profile:
public class UserProfileMap : EntityTypeConfiguration<UserProfile>
{
public UserProfileMap()
{
// Primary Key
this.HasKey(t => t.UserId);
// Properties
this.Property(t => t.Email)
.IsRequired()
.HasMaxLength(56);
this.Property(t => t.UserName)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("UserProfile");
this.Property(t => t.UserId).HasColumnName("UserId");
this.Property(t => t.Email).HasColumnName("Email");
this.Property(t => t.UserName).HasColumnName("UserName");
this.Property(t => t.UserType).HasColumnName("UserType");
this.HasOptional(e => e.UserType1).WithRequired();
The model mapping data for usertypes looks like this:
public class UserType1 : EntityTypeConfiguration<UserType1>
{
public UserType1Map()
{
// Primary Key
this.HasKey(t => t.UserId);
// Properties
this.Property(t => t.UserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.HasRequired(t => t.UserProfile).WithOptional();
this.Property(t => t.Company)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("UserType1");
this.Property(t => t.UserId).HasColumnName("UserId");
this.Property(t => t.Company).HasColumnName("Company");
// Relationships
this.HasRequired(t => t.UserProfile).WithOptional();
}
}
But I always get this error Unable to determine the principal end of an association between the types 'myApp.Models.UserType1' and 'myApp.Models.UserProfile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
what did I miss?
Configure the relationship for only one entity (in your case, in UserProfileMap). Explicitly specify the property in the .WithRequired() call as well. Here's demo code that worked for me:
modelBuilder.Entity<SharedKeyRequired>()
.HasOptional( skr => skr.SharedKeyOptional )
.WithRequired( sko => sko.SharedKeyRequired );
public class SharedKeyRequired
{
public int SharedKeyRequiredId { get; set; }
public virtual SharedKeyOptional SharedKeyOptional { get; set; }
}
public class SharedKeyOptional
{
public int SharedKeyOptionalId { get; set; }
public virtual SharedKeyRequired SharedKeyRequired { get; set; }
}
Moho, I voted your answer as correct but I thought I would put MVC equivalent source here for those that might be confused by the verbage.
Desired end result is using the MVC AccountModel and adding code-first tables which have a foreignkey as their primary keys to extend the UserProfile table with an optional 1 to 1 relationship
Modify your user profile class to add a virtual reference to your new table
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual UserInfo UserInfo { get; set; }
}
create your new table I chose to use Data Notation you could specify this in modelBuilder as
modelBuilder.Entity().HasKey(k => k.UserId);
modelBuilder.Entity().Property(ui => ui.UserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
public class UserInfo
{
[Key,ForeignKey("UserProfile")] //use above or specify this
public int UserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
public int somevalue { get; set; }
public string name { get; set; }
}
override your OnModelCreating member of your context class and specify the relationship as Moho stated
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// modelBuilder.Entity<UserInfo>().HasKey(k => k.UserId);
// modelBuilder.Entity<UserInfo>().Property(ui => ui.UserId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<UserProfile>().HasOptional(ui => ui.UserInfo).WithRequired(up => up.UserProfile);
base.OnModelCreating(modelBuilder);
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<UserInfo> UserInfoes { get; set; }
}
I am getting the following error
One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'UserRoles_Roles_Source' in relationship 'UserRoles_Roles'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
My entities and the associated mappings are defined as follows,
public class UserProfile
{
public UserProfile()
{
UserUserRoles = new List<UserRoles>();
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<UserRoles> UserUserRoles { get; set; }
}
public class Roles
{
public Roles()
{
RoleUserRoles = new List<UserRoles>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserRoles> RoleUserRoles { get; set; }
}
public class UserRoles
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual UserProfile User { get; set; }
public virtual Roles Roles { get; set; }
}
//Mappings
public UserProfileMap()
{
// Primary Key
HasKey(t => t.UserId);
// Properties
Property(t => t.UserName)
.HasMaxLength(56);
// Table & Column Mappings
ToTable("UserProfile");
Property(t => t.UserId).HasColumnName("UserId");
Property(t => t.UserName).HasColumnName("UserName");
}
public class RolesMap : EntityTypeConfiguration<Roles>
{
public RolesMap()
{
// Primary Key
HasKey(t => t.RoleId);
// Properties
Property(t => t.RoleName)
.HasMaxLength(256);
// Table & Column Mappings
ToTable("webpages_Roles");
Property(t => t.RoleId).HasColumnName("RoleId");
Property(t => t.RoleName).HasColumnName("RoleName");
}
}
public class UserRolesMap : EntityTypeConfiguration<UserRoles>
{
public UserRolesMap()
{
// Primary Key
HasKey(t => t.UserId);
HasKey(t => t.RoleId);
// Table & Column Mappings
ToTable("webpages_UsersInRoles");
Property(t => t.UserId).HasColumnName("UserId");
Property(t => t.RoleId).HasColumnName("RoleId");
// Relationships
HasRequired(t => t.User)
.WithMany(t => t.UserUserRoles)
.HasForeignKey(d => d.UserId);
HasRequired(t => t.Roles)
.WithMany(t => t.RoleUserRoles)
.HasForeignKey(d => d.RoleId);
}
}
what am I doing wrong?
Per the comments:
A combined primary key should be constructed as HasKey(t => new { t.UserId, t.RoleId });
It looks like you want to create a many-to-many relation between models. In that case you do not need the class UserRoles, it will be generated by EF when using this scenario:
public class User
{
// other properties
public virtual IList<Role> Roles {get;set;}
}
public class Role
{
// other properties
public virtual IList<User> Users {get;set;}
}
However, if you do want the UserRoles model to for instance store additional information you need to make the mapping manually in for example the OnModelCreating() function of the DbContext class.
I Try to map this simple model with TPH inheritance :
public abstract class Master {
public long Id {
get;
set;
}
public virtual ICollection<Detail> Details {
get;
set;
}
}
public class MasterA : Master {
public string FieldA {
get;
set;
}
}
public class MasterB : Master {
public string FieldB {
get;
set;
}
}
public abstract class Detail {
public long Id {
get;
set;
}
public long MasterId {
get;
set;
}
public Master Master {
get;
set;
}
public String CommonDetailInfo {
get;
set;
}
}
public class DetailA : Detail {
public MasterA MasterA {
get;
set;
}
public string SpecificA {
get;
set;
}
}
public class DetailB : Detail {
public MasterB MasterB {
get;
set;
}
public string SpecificB {
get;
set;
}
}
Mapping is done with fluent Notation Like this :
modelBuilder.Entity<Master>().ToTable("TestMaster");
modelBuilder.Entity<Master>().Property(m => m.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Master>().HasKey(m => m.Id);
modelBuilder.Entity<Master>().Map<MasterA>(m => m.Requires("MasterType").HasValue("A"));
modelBuilder.Entity<Master>().Map<MasterB>(m => m.Requires("MasterType").HasValue("B"));
modelBuilder.Entity<Detail>().ToTable("TestDetail");
modelBuilder.Entity<Detail>().Property(d => d.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Detail>().HasKey(d => d.Id);
modelBuilder.Entity<Detail>().Map<DetailA>(m => m.Requires("DetailType").HasValue("A"));
modelBuilder.Entity<Detail>().Map<DetailB>(m => m.Requires("DetailType").HasValue("B"));
modelBuilder.Entity<Master>()
.HasMany(m => m.Details)
.WithRequired(d => d.Master)
.HasForeignKey(f => f.MasterId)
.WillCascadeOnDelete();
If I let EF create my database, two fields are added in TestDetail table :
MasterA_Id
MasterB_Id
But these two fields are always Null and redundant because MasterId field on base Dteail Class do the same job ?
If I remove these fields from database and try to get Details on a master record like this :
foreach (var detail in master.Details) {...}
An Exception is raised : Invalid ColumnName MasterA_Id, MasterB_Id when I access "Details" property.
What I'm doing wrong ?
How can I map this model in TPH mode without having these two fields in database ?
Thanks for your help.
Say I have an entity model aggregate for Activity, like so:
public class Activity : Entity
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual ICollection<ActivityTag> Tags { get; set; }
}
public class ActivityTag : Entity
{
public int ActivityPersonId { get; set; }
public int ActivityNumber { get; set; }
public virtual Activity Activity { get; set; }
public int Number { get; set; }
public string Text { get; set; }
}
Forget about the relation between Activity and Person, but note the 1..* relation between Activity and ActivityTag. The fluent mapping looks more or less like this:
public class ActivityOrm : EntityTypeConfiguration<Activity>
{
public ActivityOrm()
{
ToTable("Activity", "Activities");
HasKey(p => new { p.PersonId, p.Number });
HasRequired(d => d.Person)
.WithMany()
.HasForeignKey(d => d.PersonId)
.WillCascadeOnDelete(true);
HasMany(p => p.Tags)
.WithRequired(d => d.Activity)
.HasForeignKey(d => new { d.ActivityPersonId, d.ActivityNumber })
.WillCascadeOnDelete(true);
Property(p => p.Content).HasColumnType("ntext");
}
}
public class ActivityTagOrm : EntityTypeConfiguration<ActivityTag>
{
public ActivityTagOrm()
{
ToTable("ActivityTag", "Activities");
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
Property(p => p.Text).IsRequired().HasMaxLength(500);
}
}
Given this, I want to introduce a new collection property to the Activity entity:
public ICollection<DraftedTag> DraftedTags { get; set; }
The DraftedTag entity should have the same exact properties and primary key as ActivityTag. The only thing that should be different is the table it is mapped to. I tried creating a class that derived from ActivityTag, like so:
public class DraftedTag : ActivityTag
{
}
public class DraftedTagOrm : EntityTypeConfiguration<DraftedTag>
{
public DraftedTagOrm()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("DraftedTag", "Activities");
});
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
}
}
The DraftedTagOrm has been added to the modelBuilder.Configurations collection, but without even adding the foreign key association to Activity, I get the following exception:
The property 'ActivityPersonId' is not a declared property on type
'DraftedTag'. 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.
When I completely duplicate the code from the ActivityTag class and the ActivityTagOrm constructor into the respective DraftTag class / configuration constructor, then it works as expected -- I get two different tables with identical schemas, but different names. However each time I want to make a change to the ActivityTag class, I must make a corresponding change in the DraftTag class.
Is it possible to make this code DRYer by having DraftTag extend ActivityTag? If so, what would the EntityTypeConfiguration look like for DraftTag?