I have the following class and table structure:
A component can have 1 Person (PersonID)
A component can have 1 Group (GroupID)
A component can have 1 Person and 1 Group
A Person always has a Component
A Group always has a Component
public class Component{
public Person Person {get; set;}
public Group Group { get; set; }
}
public class Person{
public Component Component { get; set; }
}
public class Group{
public Component Component { get; set; }
}
And I map this structure in this way:
public ComponentMap()
{
this.ToTable("Component")
.Property(x => x.Id)
.HasColumnName("ComponentID");
}
public GroupMap()
{
this.ToTable("Group")
.Property(x => x.Id)
.HasColumnName("GroupID");
this.HasRequired(x => x.Component)
.WithOptional(x => x.Group);
}
public PersonMap()
{
this.ToTable("Person")
.Property(x => x.Id)
.HasColumnName("GroupID");
this.HasRequired(x => x.Component)
.WithOptional(x => x.Person);
}
If I select a Group or Person the related Component is always fetched correctly but when I select a Component, the Group ID and Person ID are fetched using the Component ID and not the relative PersonID or GroupID. Where I can say that the Table Component has PersonID and GroupID? Inside the Component Map?
The tables are
**Component**
ComponentID
PersonID
GroupID
**Person**
PersonID
**Group**
GroupID
The only solution I got to make this working correctly is the following Mapping
this.ToTable("Component")
.Property(x => x.Id)
.HasColumnName("PartyID");
this.HasRequired(x => x.Person)
.WithRequiredDependent(x => x.Component)
.Map(cfg => cfg.MapKey("PersonID"));
this.HasRequired(x => x.Organization)
.WithRequiredDependent(x => x.Component)
.Map(cfg => cfg.MapKey("OrganizationID"));
Adding Properties of type FK on an entity doesn't make any difference than mapping using strings, I think is also wrong because expose useless ID on my Domain objects
If you define the FK as one of the properties in the class EF is smart enough to map it for you. You need to follow the convention of using the property name and append 'Id' to the end of it.
public class Component
{
public Person Person {get; set;}
public int PersonId {get;set;}
public Group Group { get; set; }
public int GroupId {get;set;}
}
public class Person{
public Component Component { get; set; }
public int ComponentId {get;set;}
}
public class Group
{
public Component Component { get; set; }
public int ComponentId {get;set;}
}
Is there a reason you're doing the mapping manually instead of using migrations?
Related
I have three entities as shown here:
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Application> Applications { get; set; }
}
Join entity
public class UserApplication
{
public int UserId { get; set; }
public int ApplicationId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
}
OnModelCreating section =>
modelBuilder.Entity<User>()
.HasMany(x => x.Applications)
.WithMany(x => x.Users)
.UsingEntity(ua => ua.ToTable("UserApplication"));
modelBuilder.Entity<UserApplication>()
.HasKey(a=> new { a.ApplicationId, a.UserId});
Running the code is causing an error
invalid object name => ApplicationUser.
Note - while OnModelCreating only entity with wrong name is there. DB Has table with name UserApplication
You are using mixture of explicit and implicit join entity. I'm afraid EF Core assumes 2 separate many-to-many relationships with 2 separate tables. Note that by convention the implicit join entity name is {Name1}{Name2} with names being in ascending order, which in your case is ApplicationUser.
What you need is to use the the generic overload of UsingEntity fluent API and pass the explicit join entity type as generic type argument. Also configure the join entity there instead of separately. e.g.
modelBuilder.Entity<User>()
.HasMany(x => x.Applications)
.WithMany(x => x.Users)
.UsingEntity<UserApplication>(
// ^^^
ua => ua.HasOne(e => e.Application).WithMany().HasForeignKey(e => e.ApplicationId),
ua => ua.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId),
ua =>
{
ua.ToTable("UserApplication");
ua.HasKey(a => new { a.ApplicationId, a.UserId });
});
Is it possible to configure one to one relationship using fluent api on database which does not meet convention requirements?
Below I give you sample of database and generated models.
Be aware of that tables do not define any constraints and indices except primary keys.
Tables:
create table Person (
PersonKey int primary key
)
create table Address (
AddressKey int primary key,
owner int not null // normally should be foreign key to Person
)
Code first models generated from db:
public partial class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonKey { get; set; }
}
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AddressKey { get; set; }
public int Owner { get; set; }
}
To be able to navigate from Address to Person, navigation property was added to Address class:
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AddressKey { get; set; }
public int Owner { get; set; }
public virtual Person Person { get; set; }
}
If program tries execute this query:
var Addresss = context.Addresss.Include(x => x.Person).ToList();
runtime raises exception: "Invalid column name 'Person_PersonKey'". Because context do not configure any custom mappings it tries to find foreign key by convention but Owner property does not meet convention requirements, hence the exception. So there is a need to add mappings.
If relationship between Person and Address would be one to many we could add such a configuration:
modelBuilder.Entity<Address>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.Owner);
and query defined above would execute correctly. But what if Person class would have navigation property to Address so we would have bidirectional one to one relation:
public partial class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonKey { get; set; }
public virtual Address Address { get; set; }
}
So above configuration will not work and my question is, is it possible to configure it without changing db and property names and if yes what configuration needs to be applied using only fluent api?
Here is my suggested code, I hope I understand you correctly!
public partial class Person
{
public int PersonKey { get; set; }
public Address Address {get;set;}
}
public partial class Address
{
public virtual Person Person { get; set; }
public int PersonId { get; set; }
public string AddressInfo {get;set;}
}
modelBuilder.Entity<Person>()
.HasKey(a => a.PersonKey);
modelBuilder.Entity<Course>()
.Property(c => c.CourseId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Address>()
.HasKey(a => a.PersonId);
modelBuilder.Entity<Person>()
.HasRequired(p => p.Address)
.WithRequiredPrincipal(a => a.PersonId);
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; }
}
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?
I have a base class for objects that are audited:
AuditableObject
public class AuditableObject : DomainObject, IAuditable
{
... some fields
public AuditInfo AuditInfo
{
get;
set;
}
}
AuditInfo
public class AuditInfo : IAuditable
{
public int CreatedByDbId
{
get;
set;
}
public DateTime CreatedDate
{
get;
set;
}
public int? AmendedByDbId
{
get;
set;
}
public DateTime? AmendedDate
{
get;
set;
}
}
The CreatedByDbId and AmendedByDbId are linked to a SystemUser object:
SystemUser
public class SystemUser
{
public int SystemUserDbId
{
get;
set;
}
public string Username
{
get;
set;
}
}
I have a class Call that inherits from AuditableObject, which also has other SystemUser properties:
public class Call : AuditableObject
{
... some fields
public SystemUser TakenBy { get; set;}
public SystemUser CreatedBy { get; set; }
public SystemUser CancelledBy { get; set;}
public int CancelledByDbId {get; set;}
public int TakenByDbId { get; set;}
}
Call database table
CREATE TABLE [dbo].[Call](
[CallDbId] [int] IDENTITY(1,1) NOT NULL,
[CancelledBy] [int] NULL,
[CreatedByDbId] [int] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[AmendedByDbId] [int] NULL,
[AmendedDate] [datetime] NULL,
[TakenBy] [int] NOT NULL)
I cannot seem to get my mappings right, e.g.
modelBuilder.ComplexType<AuditInfo>();
...// configuration for Call
this.Property(x => x.AuditInfo.AmendedByDbId).HasColumnName("AmendedByDbId");
this.Property(x => x.AuditInfo.AmendedDate).HasColumnName("AmendedDate");
this.Property(x => x.AuditInfo.CreatedByDbId).HasColumnName("CreatedByDbId");
this.Property(x => x.AuditInfo.CreatedDate).HasColumnName("CreatedDate");
this.Property(t => t.CancelledByDbId).HasColumnName("CancelledBy");
this.Property(t => t.TakenByDbId).HasColumnName("TakenBy");
this.HasRequired(t => t.TakenBy).WithMany().HasForeignKey(x => x.TakenByDbId);
this.HasRequired(t => t.CancelledBy).WithMany().HasForeignKey(x => x.CancelledByDbId);
and I always get errors at runtime, such as:
Invalid column name 'SystemUser_SystemUserDbId'.
Invalid column name 'SystemUser_SystemUserDbId1'.
Invalid column name 'SystemUser_SystemUserDbId2'.
Invalid column name 'CreatedBy_SystemUserDbId'.
I can't figure it out :(
The last error "Invalid column name 'CreatedBy_SystemUserDbId'" could occur because there is missing a mapping of the CreatedBy navigation property to the CreatedByDbId foreign key column in the database. It should look like this:
this.HasRequired(t => t.CreatedBy)
.WithMany()
.Map(c => c.MapKey("CreatedByDbId"));
I don't know the reason for the other three errors at the moment.
Edit
Taking a closer look I have the feeling that this model will be really difficult or perhaps impossible to map.
You have a navigation property CreatedBy on the class Call but the foreign key property CreatedByDbId on a complex type AuditInfo of the base class AuditableObject. I have doubt that you can define in the mapping that these properties belong together in the same relationship to SystemUser.
Therefore the navigation property CreatedBy should be in AuditInfo where the foreign key CreatedByDbId is. But that's not possible because you cannot put a navigation property into a complex type.
Is it necessary to use a complex type for AuditInfo? Can't you put its properties directly into the AuditableObject:
public class AuditableObject : DomainObject, IAuditable
{
... some fields
public int CreatedByDbId { get; set; }
public DateTime CreatedDate { get; set; }
public int? AmendedByDbId { get; set; }
public DateTime? AmendedDate { get; set; }
}
If you do this you should be able to move the CreatedBy navigation property from the Call entity to the AuditableObject base class where the FK CreatedByDbId lives and then create the same mapping you already did for the other two navigation properties, but this time for the base class:
modelBuilder.Entity<AuditableObject>()
.HasRequired(a => a.CreatedBy)
.WithMany()
.HasForeignKey(a => a.CreatedById);