I have a set of 3 models, which is an odd many-to-many-ish relationship.
public class Metric {
public int Id { get; set; }
public string Name { get; set; }
// ...
}
public class ActionPlan {
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
//...
public virtual ICollection<Metric> Metrics { get; set; }
}
public class PlanMetric {
public int PlanId { get; set; }
public int MetricId { get; set; }
public decimal GoalValue { get; set; }
public virtual ActionPlan Plan { get; set; }
public virtual Metric Metric { get; set; }
}
I have the relationships mapped as follows:
public class PlanMetricMapping : EntityTypeConfiguration<PlanMetric> {
public PlanMetricMapping() {
ToTable("PlanMetric");
HasKey(m => new {
m.MetricId,
m.PlanId
});
Property(m => m.GoalValue)
.IsRequired()
.HasPrecision(10, 2);
HasRequired(m => m.Metric)
.WithMany()
.HasForeignKey(m => m.MetricId);
HasRequired(m => m.Plan)
.WithMany()
.HasForeignKey(m => m.PlanId);
}
}
public class ActionPlanMapping : EntityTypeConfiguration<ActionPlan> {
public ActionPlanMapping() {
ToTable("ActionPlan");
HasKey(m => m.Id);
// ...
//HasMany(m=>m.Metrics) // how do I get to this data?
}
}
The problem is
1) EF is creating an ActionPlan_Id field in my Metric table, and I'm not sure why.
2) I don't know how to set up my mapping to be able to navigation from a Plan to it's Metrics.
EF is creating an ActionPlan_Id field because you have
public virtual ICollection<Metric> Metrics { get; set; }
in your ActionPlan definition, which EF interprets as a one-to-many relationship between ActionPlan and Metric. It seems like you want
public virtual ICollection<PlanMetric> PlanMetrics { get; set; }
instead.
Then, in order to get to an ActionPlan's metrics, you could go through that collection, perhaps through a Select().
Related
I have 2 entities:
public partial class GPSdevice
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public GPSdevice()
{
}
public int ID { get; set; }
public string UserName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual Truck Truck { get; set; }
}
public partial class Truck
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Truck()
{
}
public int TruckID { get; set; }
public string TruckNo { get; set; }
public string Make { get; set; }
[ForeignKey("GPSdevice")]
public int? GPSdeviceID { get; set; }
public virtual GPSdevice GPSdevice { get; set; }
}
I want to create a relationship one-to-one-or-zero (each GPSdevice can be linked to any truck (but to one and only one) or not linked at all)
I write the following code:
modelBuilder.Entity<GPSdevice>()
.HasOptional(e => e.Truck)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
but it creates the following migration:
public override void Up()
{
AddColumn("dbo.Truck", "GPSdevice_ID", c => c.Int());
CreateIndex("dbo.Truck", "GPSdeviceID");
CreateIndex("dbo.Truck", "GPSdevice_ID");
AddForeignKey("dbo.Truck", "GPSdeviceID", "dbo.GPSdevices", "ID");
AddForeignKey("dbo.Truck", "GPSdevice_ID", "dbo.GPSdevices", "ID");
}
why it creates one more field and how to use the current field GPSdeviceID instead?
ADDED:
If I remove
public int? GPSdeviceID { get; set; }
and add MapKey:
modelBuilder.Entity<GPSdevice>()
.HasOptional(e => e.Truck)
.WithOptionalPrincipal(e=>e.GPSdevice).Map(p=>p.MapKey("GPSdeviceID"))
.WillCascadeOnDelete(false);
in result I get the following migration code:
public override void Up()
{
CreateIndex("dbo.Truck", "GPSdeviceID");
AddForeignKey("dbo.Truck", "GPSdeviceID", "dbo.GPSdevices", "ID");
}
then I get the following error:
There are no primary or candidate keys in the referenced table
'dbo.GPSdevices' that match the referencing column list in the foreign
key 'FK_dbo.Truck_dbo.GPSdevices_GPSdeviceID'. Could not create
constraint or index. See previous errors.
Use this :
public partial class GPSdevice
{
public GPSdevice()
{
}
public int ID { get; set; }
public string UserName { get; set; }
public virtual Truck Truck { get; set; }
}
public partial class Truck
{
public Truck()
{
}
public int TruckID { get; set; }
public string TruckNo { get; set; }
public string Make { get; set; }
public virtual GPSdevice GPSdevice { get; set; }
}
modelBuilder.Entity<GPSdevice>()
.HasOptional(o => o.Truck)
.WithRequired(ad => ad.GPSdevice);
modelBuilder.Entity<Truck>()
.HasKey(e => e.TruckID);
I'm trying to integrate the SimpleMembership tables with the rest of my Object Model - to manage all the entities from a single database and context.
Up to now the best recipe I've found for manually spinning up the SM tables (the entry point to combine SimpleMember with the rest of my Object Model) is found here. But, as cited in the comments section there are a couple mistakes in the code sample provided. The comments attempt to provide corrections but, due to formatted, really hard to follow.
I'm 80% the way there but getting stuck with the Foreign Key generation for the Membership table. Does the code within OnModelCreating block belong in the MyDbContext class? I'm getting a compile error on the .WithMany(u => u.Members) line.
Membership.cs
[Table("webpages_Membership")]
public class Membership
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
public DateTime? CreateDate { get; set; }
[StringLength(128)]
public string ConfirmationToken { get; set; }
public bool? IsConfirmed { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
[Required, StringLength(128)]
public string Password { get; set; }
public DateTime? PasswordChangedDate { get; set; }
[Required, StringLength(128)]
public string PasswordSalt { get; set; }
[StringLength(128)]
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
<strike>public virtual ICollection<Role> Roles { get; set; }</strike>
EDIT: Originally I added the line above to remove a compiler complaint in the extraneous code block below. Removing this attempt to create a FK to Roles will align the rest of this code so that these model classes create a Migration that generates tables for SM.
OAuthMembership.cs
[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
[Key, Column(Order = 0), StringLength(30)]
public string Provider { get; set; }
[Key, Column(Order = 1), StringLength(100)]
public string ProviderUserId { get; set; }
public int UserId { get; set; }
}
Role.cs
[Table("webpages_Roles")]
public class Role
{
[Key]
public int RoleId { get; set; }
[StringLength(256)]
public string RoleName { get; set; }
public virtual ICollection<UserProfile> UserProfiles { get; set; }
}
UserProfile.cs
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
MyDbContext.cs
public MyDbContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Membership> Membership { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<OAuthMembership> OAuthMembership { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProfile>()
.HasMany<Role>(r => r.Roles)
.WithMany(u => u.UserProfiles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
EDIT: The block below was included in one of the article's comments but seems not to be needed.
//modelBuilder.Entity<Membership>()
// .HasMany<Role>(r => r.Roles)
// .WithMany(u => u.Members)
// .Map(m =>
// {
// m.ToTable("webpages_UsersInRoles");
// m.MapLeftKey("UserId");
// m.MapRightKey("RoleId");
// });
}
}
I followed the instructions in the article, and I also took into account the the comments that suggested the article was wrong in a few ways.
I ended up with the following classes:
UserProfile.cs
[Table("UserProfile")]
public class UserProfile
{
[Key, ForeignKey("Membership")]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<WebSecurity.Role> Roles { get; set; }
public WebSecurity.Membership Membership { get; set; }
}
You should notice right away the "ForeignKey" attribute I use on the UserId column. Since the user is first created in the Membership table, my UserProfile table is the dependent table.
Membership.cs
[Table("webpages_Membership")]
public class Membership
{
//public Membership()
//{
// Roles = new List<Role>();
// OAuthMemberships = new List<OAuthMembership>();
//}
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
public DateTime? CreateDate { get; set; }
[StringLength(128)]
public string ConfirmationToken { get; set; }
public bool? IsConfirmed { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
[Required, StringLength(128)]
public string Password { get; set; }
public DateTime? PasswordChangedDate { get; set; }
[Required, StringLength(128)]
public string PasswordSalt { get; set; }
[StringLength(128)]
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public UserProfile UserProfile { get; set; }
}
Per Richard's comments in the article, I commented out the constructor. I also created a reference back to the UserProfile, but not to roles.
OAuthMembership.cs
[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
[Key, Column(Order = 0), StringLength(30)]
public string Provider { get; set; }
[Key, Column(Order = 1), StringLength(100)]
public string ProviderUserId { get; set; }
public int UserId { get; set; }
//[Column("UserId"), InverseProperty("OAuthMemberships")]
//public Membership User { get; set; }
}
My OAuthMembership class remained basically the same; I commented out only the User attribute, per Richard's comment in the article.
AccountModel.cs+UsersContext
Finally, the UserContext class, where I create the association for the UsersInRoles table.
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<InternetApplication.Models.WebSecurity.Role>()
.HasMany<InternetApplication.Models.UserProfile>(r => r.UserProfiles)
.WithMany(u => u.Roles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
}
public DbSet<WebSecurity.Membership> Membership { get; set; }
public DbSet<WebSecurity.OAuthMembership> OAuthMembership { get; set; }
public DbSet<WebSecurity.Role> Roles { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
}
In addition to adding the UsersInRoles mapping, I added DbSet entries for each table.
Now that everything has been created, I can use my Add-Migration and Update-Database commands and use the following code snippet that combines the Membership, UserProfile, and Roles tables:
using (var db = new UsersContext())
{
var memberships = db.Membership
.Include("UserProfile")
.Include("UserProfile.Roles")
.ToList();
foreach (var member in memberships)
{
member.IsConfirmed = true;
}
db.SaveChanges();
}
This was a long post, but I hope that helps.
I used the answer to this question to automatically generate the models from the existing 'webpage_' tables in my database. This ensures that the models are created in the exact same way that SimpleMembership creates them. This resulted in the following code:
Models:
public partial class webpages_Membership
{
public int UserId { get; set; }
public Nullable<System.DateTime> CreateDate { get; set; }
public string ConfirmationToken { get; set; }
public Nullable<bool> IsConfirmed { get; set; }
public Nullable<System.DateTime> LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
public string Password { get; set; }
public Nullable<System.DateTime> PasswordChangedDate { get; set; }
public string PasswordSalt { get; set; }
public string PasswordVerificationToken { get; set; }
public Nullable<System.DateTime> PasswordVerificationTokenExpirationDate { get; set; }
}
public partial class webpages_Roles
{
public webpages_Roles()
{
this.webpages_UsersInRoles = new HashSet<webpages_UsersInRoles>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<webpages_UsersInRoles> webpages_UsersInRoles { get; set; }
}
public partial class webpages_UsersInRoles
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual webpages_Roles webpages_Roles { get; set; }
}
Fluent Mappings:
internal partial class MembershipMapping : EntityTypeConfiguration<webpages_Membership>
{
public MembershipMapping()
{
this.HasKey(t => t.UserId);
this.ToTable("webpages_Membership");
this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.Property(t => t.CreateDate).HasColumnName("CreateDate");
this.Property(t => t.ConfirmationToken).HasColumnName("ConfirmationToken").HasMaxLength(128);
this.Property(t => t.IsConfirmed).HasColumnName("IsConfirmed");
this.Property(t => t.LastPasswordFailureDate).HasColumnName("LastPasswordFailureDate");
this.Property(t => t.PasswordFailuresSinceLastSuccess).HasColumnName("PasswordFailuresSinceLastSuccess");
this.Property(t => t.Password).HasColumnName("Password").IsRequired().HasMaxLength(128);
this.Property(t => t.PasswordChangedDate).HasColumnName("PasswordChangedDate");
this.Property(t => t.PasswordSalt).HasColumnName("PasswordSalt").IsRequired().HasMaxLength(128);
this.Property(t => t.PasswordVerificationToken).HasColumnName("PasswordVerificationToken").HasMaxLength(128);
this.Property(t => t.PasswordVerificationTokenExpirationDate).HasColumnName("PasswordVerificationTokenExpirationDate");
}
}
internal partial class RolesMapping : EntityTypeConfiguration<webpages_Roles>
{
public RolesMapping()
{
this.HasKey(t => t.RoleId);
this.ToTable("webpages_Roles");
this.Property(t => t.RoleId).HasColumnName("RoleId");
this.Property(t => t.RoleName).HasColumnName("RoleName").IsRequired().HasMaxLength(256);
}
}
internal partial class UsersInRolesMapping : EntityTypeConfiguration<webpages_UsersInRoles>
{
public UsersInRolesMapping()
{
this.HasKey(t => new { t.UserId, t.RoleId });
this.ToTable("webpages_UsersInRoles");
this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.Property(t => t.RoleId).HasColumnName("RoleId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.HasRequired(t => t.webpages_Roles).WithMany(t => t.webpages_UsersInRoles).HasForeignKey(d => d.RoleId);
}
}
Database Context:
public class MembershipContext : DbContext, IDisposable
{
public DbSet<webpages_Membership> Membership { get; set; }
public DbSet<webpages_Roles> Roles { get; set; }
public DbSet<webpages_UsersInRoles> UsersInRoles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MembershipMapping());
modelBuilder.Configurations.Add(new RolesMapping());
modelBuilder.Configurations.Add(new UsersInRolesMapping());
base.OnModelCreating(modelBuilder);
}
}
Note that I have excluded the OAuthMembership table, because I didn't need it for my solution. But if you follow the steps in the link I provided above you can easily include that table as well.
Starting from a blank MVC4 Internet Template I ran the project so as to create the SimpleMembership tables in a fresh db - then used EF's Reverse Engineer tool to create POCOs from those tables. Stepped thru it line by line to find the error and edited the code block in the OP.
With that code in place I used Package Manager to 'Add-Migration' and 'Update-Database'. Initial tests confirm everything works - I suppose I'll have to revisit if I find edge-cases that expose any deeper problems.
I have the following classes:
public class Bicycle
{
public int BicycleId { get; set; }
public DateTime YearOfManufacture { get; set; }
public int BicycleManufactuerId { get; set; }
public BicycleManufacturer BicycleManufacturer { get; set; }
}
public class BicycleManufacturer
{
public int BicycleManufacturerId { get; set; }
public string Description { get; set; }
}
Each Bicycle must have a BicycleManufacturer (1:1). There could be some BicycleManufacturer that isn't associate with any Bicycle. Most will be associated with multiple Bicycle entities.
I have the following fluent API code to set up the FK relationship:
modelBuilder.Entity<Bicycle>()
.HasRequired(a => a.BicycleManufacturer)
.WithMany()
.HasForeignKey(u => u.BicycleManufactuerId);
This all seems to work fine. However, I would really like to remove the BicycleManufacturerId property from the Bicycle entity. It's only there to establish the FK relationship. Is there a way I can create the proper FK relationship if I remove this property?
You can remove the property and use the mapping:
modelBuilder.Entity<Bicycle>()
.HasRequired(a => a.BicycleManufacturer)
.WithMany()
.Map(m => m.MapKey("BicycleManufactuerId"));
You can also do it by convention by adding the relationship on the other side as a collection.
public class Bicycle
{
public int BicycleId { get; set; }
public DateTime YearOfManufacture { get; set; }
public int BicycleManufactuerId { get; set; }
public BicycleManufacturer BicycleManufacturer { get; set; }
}
public class BicycleManufacturer
{
public int BicycleManufacturerId { get; set; }
public ICollection<Bicycle> Bicycles { get; set; }
public string Description { get; set; }
}
I'm having issues applying multiple relationships (or possibly foreignkey) on two POCO objects. I've got the first relationship many-to-many working and when the database is created it creates the three tables (Projects, Users and ProjectsUsers) needed for the relationship.
Code so far:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Name = new Name();
}
public int UserId { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public Name Name { get; set; }
public ICollection<Project> ManagedProjects { get; set; }
}
public class ProjectConfiguration : EntityTypeConfiguration<Project>
{
public ProjectConfiguration()
{
HasMany(x => x.Users)
.WithMany(x => x.ManagedProjects);
}
}
public UserConfiguration()
{
HasMany(x => x.ManagedProjects)
.WithMany(x => x.Users);
}
Now I want to add an optional one-to-one relationship of Project.ManagingUser -> User. However, I can't seem to figure out how to indicate this in the configuration.
Code for what I think is needed:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public int? ManagingUserId { get; set; }
public User ManagingUser { get; set; }
public ICollection<User> Users { get; set; }
}
I don't think the User object needs to change.
This shows my last attempt on mapping the new relationship:
public ProjectConfiguration()
{
HasMany(p => p.Users)
.WithMany(u => u.Projects);
this.HasOptional(p => p.ManagingUser)
.WithOptionalDependent()
.Map(m=>m.MapKey("ManagingUserId"))
.WillCascadeOnDelete(false);
}
What is happening when the database is created, I now end up with only two tables (Projects and Users). And it looks like it is only trying to setup the one-to-one relationship.
Can someone tell me what I'm missing?
Richard I've not changed the UserConfiguration and below is the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext() : base(Properties.Settings.Default.ConnectionString)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Project> Projects { get; set; }
}
You probably want WithMany instead of WithOptionalDependent - it's a one:many relationship, not a one:one.
HasOptional(p => p.ManagingUser)
.WithMany()
.HasForeignKey(m => m.ManagingUserId)
.WillCascadeOnDelete(false);
EDIT
I think you're missing the OnModelCreating override from the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ProjectConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}
I'm trying to define a many-to-many relationship explicitly. By explicitly, I mean that I'm defining the middle entity and configuring it using the Fluent API. Below is my code:
public class ContentType
{
public Int64 Id { get; set; }
public Guid SID { get; set; }
public String Name { get; set; }
public ContentType Parent { get; set; }
public Nullable<Int64> ParentId { get; set; }
public Category Category { get; set; }
public Nullable<Int64> CategoryId { get; set; }
public Boolean IsLocked { get; set; }
public virtual ICollection<ContentTypeColumn> ContentTypeColumns { get; set; }
}
public class Column
{
public Int64 Id { get; set; }
public Guid SID { get; set; }
public String SchemaName { get; set; }
public DataType DataType { get; set; }
public Int32 DataTypeId { get; set; }
public virtual ICollection<ContentTypeColumn> ContentTypeColumns { get; set; }
}
public class ContentTypeColumn
{
public Int64 Id { get; set; }
public Int64 ColumnId { get; set; }
public Column Column { get; set; }
public ContentType ContentType { get; set; }
public Int64 ContentTypeId { get; set; }
public Boolean IsRequired { get; set; }
public Boolean IsCalculated { get; set; }
public String Title { get; set; }
public Boolean IsSystem { get; set; }
public Expression Expression { get; set; }
public Int32 ExpressionId { get; set; }
public virtual ICollection<ColumnRule> Rules { get; set; }
}
public class ContentTypeConfiguration : EntityTypeConfiguration<ContentType>
{
public ContentTypeConfiguration()
{
this.ToTable("ContentType");
this.Property(x => x.Id).HasColumnName("ContentTypeId").IsRequired();
this.Property(x => x.Name).HasMaxLength(30);
this.HasOptional(x => x.Parent)
.WithMany()
.HasForeignKey(x => x.ParentId);
this.Property(x => x.SID).IsRequired();
}
}
public class ContentTypeColumnConfiguration : EntityTypeConfiguration<ContentTypeColumn>
{
public ContentTypeColumnConfiguration()
{
this.ToTable("ContentTypeColumn");
this.HasRequired(x => x.ContentType)
.WithMany()
.HasForeignKey(x => x.ContentTypeId);
this.Property(x => x.Title).HasMaxLength(50).IsRequired();
this.Property(x => x.Id).HasColumnName("ContentTypeColumnId");
}
}
For some reason, two foreign keys are being created on the resultant ContentTypeColumn table. One is a nullable foreign key, the other is non-nullable. I want only the latter to be generated, and I have no idea where the nullable key is coming from.
Any thoughts?
This is wrong:
this.HasRequired(x => x.ContentType)
.WithMany()
.HasForeignKey(x => x.ContentTypeId);
You have reverse navigation property so you must use int in WithMany or EF will probably create two relations:
this.HasRequired(x => x.ContentType)
.WithMany(y => y.ContentTypeColumns)
.HasForeignKey(x => x.ContentTypeId);
Btw. this mapping should not be needed at all because it is discovered automatically through default conventions.