Entity Code First Cyclic Reference Migrations - ef-code-first

Thanks in advance! I'm having trouble getting a cyclic reference working where EntityA has a nullable foreign key to EntityB and EntityB has a non-nullable (enforced) reference to EntityA. I've created a sample to demonstrate, but I can't get the migrations to have EntityA's refernce to EntityB to be through a foreign key:
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public EntityB MasterEntityB { get; set; }
public int? MasterEntityBId { get; set; }
}
public class EntityB
{
public string Name { get; set; }
public int Id { get; set; }
public EntityA EntityA { get; set; }
public int AssociationTypeId { get; set; }
}
public class DataContext : DbContext
{
public DataContext()
: base("CyclicTest")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<EntityB>()
.HasRequired(t => t.EntityA)
.WithOptional(a => a.MasterEntityB)
.WillCascadeOnDelete(false);
}
public DbSet<EntityB> Tenants { get; set; }
public DbSet<EntityA> AssociationTypes { get; set; }
}
And here's the migration I get:
public partial class Initial : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.EntityAs",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Description = c.String(),
MasterEntityBId = c.Int(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.EntityBs",
c => new
{
Id = c.Int(nullable: false),
Name = c.String(),
AssociationTypeId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.EntityAs", t => t.Id)
.Index(t => t.Id);
}
public override void Down()
{
DropForeignKey("dbo.EntityBs", "Id", "dbo.EntityAs");
DropIndex("dbo.EntityBs", new[] { "Id" });
DropTable("dbo.EntityBs");
DropTable("dbo.EntityAs");
}
}
I can change the migration manually so it's a foreign key like so:
CreateTable(
"dbo.EntityAs",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Description = c.String(),
MasterEntityBId = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.EntityBs", t => t.MasterEntityBId);
CreateTable(
"dbo.EntityBs",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
EntityAId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.EntityAs", t => t.EntityAId)
.Index(t => t.Id);
But then I find I get an error when I insert an EntityB: "Cannot insert explicit value for identity column in table 'EntityB' when IDENTITY_INSERT is set to OFF."
Again, appreciate the help!
-Max

OK I found the problem. I didn't have a reference for the one to many from A to B via a virtual Collection. That was enough to solve the problem. Plus I've decided to use a sub class for the Master Reference so I don't need a second reference from B back to A for the master instance. That would be null for all other instances and could lead to duplicates breaking the 0 or 1 to 1 intended relationship between MasterB and A. I.e.:
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
**public ICollection<EntityB> EntityBs { get; set; }**
public MasterEntityB MasterEntityB { get; set; }
public int? MasterEntityBId { get; set; }
}
public class MasterEntityB : EntityB
{
}
public class EntityB
{
public string Name { get; set; }
public int Id { get; set; }
public EntityA EntityA { get; set; }
public int EntityAId { get; set; }
}

Related

Problem in saving the edited information in the table in ASP.NET with Entity Framework

I have designed a web form in ASP.NET using First Code, I have added a table for the user profile to the default database with the migration method. I have no problem creating a user and calling the user, but it is not saved in the table to edit the user's profile.
I have tried most of the methods suggested on this site but have not succeeded
I am using Visual Studio 2022 and Framework 6.4.4
My pages are as follows:
The Context and Table page:
public class UserContext : DbContext
{
public UserContext()
:base("DefaultConnection")
{
}
public DbSet<UserProfiles> userProfiles { get; set; }
}
public class UserProfiles
{
[Key]
public string userId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Father { get; set; }
public string PersonId { get; set; }
public string PhonNumber { get; set; }
public string BirthDate { get; set; }
public string Address { get; set; }
public int StatusId { get; set; }
}
and Migration Page is:
public partial class UserProfiles : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.UserProfiles",
c => new
{
userId = c.String(nullable: false, maxLength: 128),
FirstName = c.String(),
LastName = c.String(),
Father = c.String(),
PersonId = c.String(),
PhonNumber = c.String(),
BirthDate = c.String(),
Address = c.String(),
StatusId = c.Int(nullable: false),
})
.PrimaryKey(t => t.userId);
}
public override void Down()
{
DropTable("dbo.UserProfiles");
}
}
And Update Button code is:
protected void updateButton_Click(object sender, EventArgs e)
{
var currentId = User.Identity.GetUserId();
UserProfiles profiles;
using(UserContext context = new UserContext())
{
profiles = context.userProfiles.Where(c => c.userId == currentId).First();
}
profiles.Father = father.Text;
profiles.PersonId = personId.Text;
profiles.BirthDate = birthDate.Text;
profiles.PhonNumber = mobilNumber.Text;
profiles.Address = Address.Text;
profiles.StatusId = Convert.ToInt32(statusId.Text);
using(UserContext db = new UserContext())
{
db.Entry(profiles).State = EntityState.Modified;
db.SaveChanges();
}

EF Core Sqlite - FOREIGN KEY constraint failed migration - not nullable to nullable foreign key

I want to change a foreign key on my table from not nullable to nullable
Class name : DestinatairePartageDocument
public class DestinatairePartageDocument
{
[Key]
public int DestinatairePartageDocumentId { get; set; }
//[ForeignKey("Document")]
//public int DocumentId { get; set; }
[ForeignKey("Document")]
public int? DocumentId { get; set; }
public Document Document { get; set; }
//[ForeignKey("Examen")]
[NotMapped]
public int? ExamenId { get; set; }
//public Examen Examen { get; set; }
[ForeignKey("DestinatairePartage")]
public int DestinatairePartageId { get; set; }
public DestinatairePartage DestinatairePartage { get; set; }
public string CodeAccesDocument { get; set; }
public DateTime DateFinValiditeCodeAccesDocument { get; set; }
public string TypePartage { get; set; } /* PartageClassique, PartageCodeGenere */
public string StatutDocument { get; set; }
public bool PartageActive { get; set; }
public DateTime DateCreation { get; set; }
public DateTime DateDerniereModification { get; set; }
}
Old property setup :
[ForeignKey("Document")]
public int DocumentId { get; set; }
public Document Document { get; set; }
New property setup :
[ForeignKey("Document")]
public int? DocumentId { get; set; }
public Document Document { get; set; }
Upon doing this update, I then update my migration schema :
dotnet ef Migrations add UpdateDocumentNullable
And then I apply the modification to the database :
dotnet ef database update
But I then have the following error "FOREIGN KEY constraint failed" :
Here is the migration file generated :
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_DestinatairesPartageDocuments_Documents_DocumentId",
table: "DestinatairesPartageDocuments");
migrationBuilder.AddColumn<string>(
name: "StatutPartageExamen",
table: "Examens",
type: "TEXT",
nullable: true);
migrationBuilder.AlterColumn<int>(
name: "DocumentId",
table: "DestinatairesPartageDocuments",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.AddColumn<int>(
name: "ExamenId",
table: "DestinatairesPartageDocuments",
type: "INTEGER",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_DestinatairesPartageDocuments_Documents_DocumentId",
table: "DestinatairesPartageDocuments",
column: "DocumentId",
principalTable: "Documents",
principalColumn: "DocumentId",
onDelete: ReferentialAction.Restrict);
}
Below are the queries created from the migration :
My DbContext class :
public class ApplicationDbContext: IdentityDbContext<ApplicationUser>, IDataProtectionKeyContext
{
private readonly ILoggerFactory _loggerFactory;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ILoggerFactory loggerFactory) : base(options)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(_loggerFactory);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//modelBuilder.Entity<DestinatairePartageDocument>()
// .Property(dpd => dpd.DocumentId)
// .HasDefaultValueSql("NULL");
//modelBuilder.Entity<DestinatairePartageDocument>()
// .HasOne(dpd => dpd.Document)
// .WithMany()
// .OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<DestinatairePartageDocument>()
// .HasOne("PortailWorker.Models.Document", "Document")
// .WithMany()
// .HasForeignKey("DocumentId");
modelBuilder.Entity<DestinatairePartageDocument>()
.Property(ap => ap.DateCreation)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
modelBuilder.Entity<DestinatairePartageDocument>()
.Property(ap => ap.DateDerniereModification)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
modelBuilder.Entity<Revendeur>()
.HasMany(r => r.ListeClients)
.WithOne(c => c.Revendeur);
modelBuilder.Entity<ClientWPFFetcher>()
.HasKey(cwf => new { cwf.ClientId, cwf.WPFFetcherId });
modelBuilder.Entity<ClientWPFFetcher>()
.Property(cwf => cwf.RelationActivated)
.HasDefaultValue(true);
modelBuilder.Entity<ClientWPFFetcher>()
.HasOne(cwf => cwf.Client)
.WithMany(c => c.ClientWPFFetchers)
.HasForeignKey(cwl => cwl.ClientId);
modelBuilder.Entity<ClientWPFFetcher>()
.HasOne(cwf => cwf.WPFFetcher)
.WithMany(c => c.ClientWPFFetchers)
.HasForeignKey(cwl => cwl.WPFFetcherId);
modelBuilder.Entity<ClientWorkerLocal>()
.HasKey(cwl => new { cwl.ClientId, cwl.WorkerLocalId });
modelBuilder.Entity<ClientWorkerLocal>()
.Property(cwl => cwl.RelationActivated)
.HasDefaultValue(true);
modelBuilder.Entity<ClientWorkerLocal>()
.HasOne(cwl => cwl.Client)
.WithMany(c => c.ClientWorkersLocal)
.HasForeignKey(cwl => cwl.ClientId);
modelBuilder.Entity<ClientWorkerLocal>()
.HasOne(cwl => cwl.WorkerLocal)
.WithMany(c => c.ClientWorkersLocal)
.HasForeignKey(cwl => cwl.WorkerLocalId);
modelBuilder.Entity<Client>()
.Property(ap => ap.ActivationStatus)
.HasDefaultValue(1);
modelBuilder.Entity<Revendeur>()
.Property(ap => ap.ActivationStatus)
.HasDefaultValue(1);
modelBuilder.Entity<WorkerLocal>()
.Property(ap => ap.ActivationStatus)
.HasDefaultValue(1);
modelBuilder.Entity<ConfigurationWorker>()
.Property(cw => cw.Jpeg2000LossyRate)
.HasDefaultValue(40);
modelBuilder.Entity<ConfigurationWorker>()
.Property(cw => cw.ActiverBackgroundServiceWorker)
.HasDefaultValue(true);
modelBuilder.Entity<WorkerLocal>()
.Property(ap => ap.EnLigne)
.HasDefaultValue(false);
modelBuilder.Entity<WorkerLocal>()
.Property(ap => ap.UtilisationCPUPourcentage)
.HasDefaultValue(0.00);
modelBuilder.Entity<WorkerLocal>()
.Property(ap => ap.UtilisationRAMPourcentage)
.HasDefaultValue(0.00);
modelBuilder.Entity<WorkerLocal>()
.Property(ap => ap.UtilisationStockagePourcentage)
.HasDefaultValue(0.00);
modelBuilder.Entity<MappingHL7>()
.Property(m => m.SegmentId)
.HasDefaultValue(1);
modelBuilder.Entity<MappingHL7>()
.Property(m => m.ChampsHL7RepetitionId)
.HasDefaultValue(1);
modelBuilder.Entity<MappingHL7>()
.Property(m => m.SousComposantHL7)
.HasDefaultValue(1);
modelBuilder.Entity<MappingHL7>()
.Property(m => m.TypeMessage)
.HasDefaultValue("Tous");
}
public DbSet<RegleTraitementImage> ReglesTraitementImage { get; set; }
public DbSet<CritereSelectionRegle> CriteresSelectionRegle { get; set; }
public DbSet<AETDemandeur> AETDemandeurs { get; set; }
public DbSet<DestinatairePartageDocument> DestinatairesPartageDocuments { get; set; }
public DbSet<WPFFetcher> WPFFetchers { get; set; }
public DbSet<ClientWPFFetcher> ClientsWPFFetchers { get; set; }
public DbSet<ExamenAEnvoyer> ExamensAEnvoyer { get; set; }
public DbSet<ImageAEnvoyer> ImagesAEnvoyer { get; set; }
public DbSet<WorkerLocal> WorkersLocal { get; set; }
public DbSet<DestinationHL7> DestinationsHL7 { get; set; }
public DbSet<MessageHL7> MessagesHL7 { get; set; }
public DbSet<Patient> Patients { get; set; }
public DbSet<Examen> Examens { get; set; }
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Correspondant> Correspondants { get; set; }
public DbSet<ExamensCorrespondants> ExamensCorrespondants { get; set; }
public DbSet<MappingHL7> MappingHL7 { get; set; }
public DbSet<ConfigurationWorker> ConfigurationWorker { get; set; }
public DbSet<DocumentStatus> DocumentStatus { get; set; }
public DbSet<Document> Documents { get; set; }
public DbSet<DocumentLogEvents> DocumentLogEvents { get; set; }
public DbSet<DestinatairePartage> DestinatairesPartage { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Revendeur> Revendeurs { get; set; }
public DbSet<ClientWorkerLocal> ClientWorkersLocal { get; set; }
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
public DbSet<MappingExamenCompression> MappingExamenCompression { get; set; }
}
Has anyone any idea how to prevent this error from happening ?
I have tried changing the onDelete constraint but it didn't change anything.
Thanks for your help !
After clearing the table everything works fine.
I think I had foreign key id from my previous tests that didn't exist anymore in the original table.
Sorry to bother you !

Can't create many to many relationship in ef core with ApplicationUser

I'm trying to create a many to many relationship between ApplicationUser and Courses in my application but I'm getting the following error when attempting to insert a record into the CourseUsers table:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_CourseUsers_AspNetUsers_UserId". The conflict occurred in database
"PIL", table "dbo.AspNetUsers", column 'Id'. The statement has been terminated.
I can't figure out what the issue is. I've followed the documentation here: https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key
My user model:
public class ApplicationUser : IdentityUser
{
public DateTime Created { get; set; }
public DateTime LastActive { get; set; }
public List<CourseUser> CourseUsers { get; set; }
public List<UserRole> UserRoles { get; set; }
}
My course model:
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Subtitle { get; set; }
public string ThumbnailUrl { get; set; }
public string PreviewVideoUrl { get; set; }
public string Topics { get; set; }
public decimal Cost { get; set; }
public List<CourseContributor> CourseContributors { get; set; }
public List<CourseUser> CourseUsers { get; set; }
public List<Lesson> Lessons { get; set; }
public string Grades { get; set; }
}
My lookup model:
public class CourseUser
{
public int CourseId { get; set; }
public Course Course { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
My DataContext:
public class DataContext : IdentityDbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Contributor> Contributors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CourseContributor>()
.HasKey(c => new { c.CourseId, c.ContributorId });
modelBuilder.Entity<CourseContributor>()
.HasOne(cc => cc.Contributor)
.WithMany(c => c.CourseContributors)
.HasForeignKey(ct => ct.ContributorId);
modelBuilder.Entity<CourseContributor>()
.HasOne(cc => cc.Course)
.WithMany(c => c.CourseContributors)
.HasForeignKey(ct => ct.CourseId);
modelBuilder.Entity<CourseUser>()
.HasKey(c => new { c.CourseId, c.UserId });
modelBuilder.Entity<CourseUser>()
.HasOne(u => u.User)
.WithMany(c => c.CourseUsers)
.HasForeignKey(us => us.UserId);
modelBuilder.Entity<CourseUser>()
.HasOne(cc => cc.Course)
.WithMany(c => c.CourseUsers)
.HasForeignKey(ct => ct.CourseId);
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Lesson>().HasData(new Lesson() { Id = 1, Title = "Test Lesson", SubTitle = "Test Lesson Title", Description = "Test Lesson Description", DocUrl = "fdsafdsffds", ThumbnailUrl = "https://media.gettyimages.com/photos/stack-of-books-picture-id157482029?s=612x612", VideoUrl = "testVideoUrl", CourseId = 1 });
modelBuilder.Entity<Contributor>().HasData(new Contributor() { Id = 1, FirstName = "Test First Name", LastName = "Test Last Name" });
modelBuilder.Entity<Course>().HasData(new Course() { Id = 1, Title = "Test Course", Subtitle = "Test Course Title", Description = "Test Course Description", Cost = 20.00M, Grades = "1,2,3", PreviewVideoUrl = "Test Preview Video Url", ThumbnailUrl = "https://media.gettyimages.com/photos/stack-of-books-picture-id157482029?s=612x612", Topics = "Test Topic 1|Test Topic 2|Test Topic 3" });
modelBuilder.Entity<CourseContributor>().HasData(new CourseContributor() { CourseId = 1, ContributorId = 1 });
modelBuilder.Entity<CourseUser>().HasData(new CourseUser() { CourseId = 1, UserId = "1234" });
}
}
EDIT: Here's the User seeding method
public class Seed
{
public static void SeedUsers(UserManager<ApplicationUser> userManager)
{
if (!userManager.Users.Any())
{
userManager.CreateAsync(new ApplicationUser { Id = "1234", UserName = "test#test.org", Created = DateTime.UtcNow, LastActive = DateTime.UtcNow, Email = "test#test.org" }, "Password1!").Wait();
}
}
}
And Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<DataContext>();
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
context.Database.Migrate();
Seed.SeedUsers(userManager);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occured during migration");
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
There isn't really an issue here with the model you defined. Its just your test data, that is incomplete.
Add the following line to your OnModelCreating() method:
modelBuilder.Entity<ApplicationUser>().HasData(new ApplicationUser {Id = "1234"});

EF Code First - Invalid column name

Im getting an error "Invalid column name 'FeeLevel_LevelId' which makes absolutely no sense considering all properties are simple types and there is no FeeLevel nor a LevelId object in this object.
So my context is:
public partial class FeesDbContext : DisconnectedEntityContext
{
public DbSet<Currency> Currencies { get; set; }
public DbSet<FeeLevel> FeeLevels { get; set; }
public DbSet<FeeLevelDetail> FeeLevelDetails { get; set; }
public DbSet<FeeType> FeeTypes { get; set; }
public DbSet<MemberFeeDiscountLevel> MemberFeeDiscountLevels { get; set; }
public FeesDbContext()
: base("FeesDb") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FeeLevelMap());
modelBuilder.Configurations.Add(new FeeLevelDetailMap());
modelBuilder.Configurations.Add(new FeeTypeMap());
modelBuilder.Configurations.Add(new CurrencyMap());
modelBuilder.Configurations.Add(new MemberFeeDiscountLevelMap());
}
public static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new FeesDbContext())
{
context.Set<TEntity>().Add(root);
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
}
The POCO is:
public partial class MemberFeeDiscountLevel : AbstractState
{
public long MemberFeeDiscountLevelId { get; set; }
public System.Guid MemberId { get; set; }
public short MemberAsType { get; set; }
public long FeeDiscountLevelId { get; set; }
public System.DateTime FeeDiscountLevelAppliedDate { get; set; }
public Nullable<System.DateTime> FeeDiscountLevelExpiresDate { get; set; }
public Nullable<long> FallbackFeeDiscountLevelId { get; set; }
public System.Guid UserId { get; set; }
public System.DateTime LastModified { get; set; }
public MemberFeeDiscountLevel(ObjectState state) : base(state) { }
public MemberFeeDiscountLevel()
{
}
}
It's mapping is:
public class MemberFeeDiscountLevelMap : EntityTypeConfiguration<MemberFeeDiscountLevel>
{
public MemberFeeDiscountLevelMap()
{
// Primary Key
this.HasKey(t => t.MemberFeeDiscountLevelId);
this.Ignore(t => t.State);
// Properties
// Table & Column Mappings
this.ToTable("MemberFeeDiscountLevel");
this.Property(t => t.MemberFeeDiscountLevelId).HasColumnName("MemberFeeDiscountLevelId");
this.Property(t => t.MemberId).HasColumnName("MemberId");
this.Property(t => t.MemberAsType).HasColumnName("MemberAsType");
this.Property(t => t.FeeDiscountLevelId).HasColumnName("FeeDiscountLevelId");
this.Property(t => t.FeeDiscountLevelAppliedDate).HasColumnName("FeeDiscountLevelAppliedDate");
this.Property(t => t.FeeDiscountLevelExpiresDate).HasColumnName("FeeDiscountLevelExpiresDate");
this.Property(t => t.FallbackFeeDiscountLevelId).HasColumnName("FallbackFeeDiscountLevelId");
this.Property(t => t.UserId).HasColumnName("UserId");
this.Property(t => t.LastModified).HasColumnName("LastModified");
}
}
The database table is:
and it has not relationships. Yet EF is generating the following SQL:
exec sp_executesql N'INSERT [dbo].[MemberFeeDiscountLevel]([MemberId], [MemberAsType], [FeeDiscountLevelId], [FeeDiscountLevelAppliedDate], [FeeDiscountLevelExpiresDate], [FallbackFeeDiscountLevelId], [UserId], [LastModified], [FeeLevel_LevelId])
VALUES (#0, #1, #2, #3, #4, #5, #6, #7, #8, NULL)
SELECT [MemberFeeDiscountLevelId]
FROM [dbo].[MemberFeeDiscountLevel]
WHERE ##ROWCOUNT > 0 AND [MemberFeeDiscountLevelId] = scope_identity()',N'#0 uniqueidentifier,#1 smallint,#2 bigint,#3 datetime2(7),#4 datetime2(7),#5 bigint,#6 uniqueidentifier,#7 datetime2(7),#8 int',#0='DAF771D1-079F-4743-B5C7-FD0FA1C63E19',#1=0,#2=1012,#3='2014-01-24 12:05:36.0608347',#4='2014-02-01 00:00:00',#5=1018,#6='EEDF2C83-2123-4B1C-BF8D-BE2D2FA26D09',#7='2014-01-24 12:05:36.0608347'
go
UPDATE:
Creating a new Fees2DbContext stripping out the other DbSets "fixes" the problem.... but I dont know why... none of these classes / sets are related to the class in question.
public partial class Fees2DbContext : DisconnectedEntityContext
{
public DbSet<MemberFeeDiscountLevel> MemberFeeDiscountLevels { get; set; }
public Fees2DbContext()
: base("FeesDb") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MemberFeeDiscountLevelMap());
}
public static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new Fees2DbContext())
{
context.Set<TEntity>().Add(root);
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
}
UPDATE 2:
public partial class FeeLevel : AbstractState
{
public FeeLevel()
{
this.FeeLevelDetails = new List<FeeLevelDetail>();
this.MemberFeeDiscountLevels = new List<MemberFeeDiscountLevel>();
}
public long LevelId { get; set; }
public string LevelName { get; set; }
public int CurrencyId { get; set; }
public System.DateTime LastModified { get; set; }
public bool IsSystemOwned { get; set; }
public System.Guid UserId { get; set; }
public virtual Currency Currency { get; set; }
[ScriptIgnore]
public virtual ICollection<FeeLevelDetail> FeeLevelDetails { get; set; }
public virtual ICollection<MemberFeeDiscountLevel> MemberFeeDiscountLevels { get; set; }
}
public class FeeLevelMap : EntityTypeConfiguration<FeeLevel>
{
public FeeLevelMap()
{
// Primary Key
this.HasKey(t => t.LevelId);
this.Ignore(t => t.State);
// Properties
this.Property(t => t.LevelId);
// .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(t => t.LevelName)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("FeeLevel");
this.Property(t => t.LevelId).HasColumnName("LevelId");
this.Property(t => t.LevelName).HasColumnName("LevelName");
this.Property(t => t.CurrencyId).HasColumnName("CurrencyId");
this.Property(t => t.LastModified).HasColumnName("LastModified");
this.Property(t => t.UserId).HasColumnName("UserId");
// Relationships
this.HasRequired(t => t.Currency)
.WithMany(t => t.FeeLevels)
.HasForeignKey(d => d.CurrencyId);
}
}
FeeLevel.MemberFeeDiscountLevels is a navigation property and it introduces a one-to-many relationship between FeeLevel and MemberFeeDiscountLevel: A FeeLevel can have many MemberFeeDiscountLevels which means at the same time that a MemberFeeDiscountLevel has a single FeeLevel. Although you don't have a navigation and foreign key property in MemberFeeDiscountLevel the database must have a foreign key in the MemberFeeDiscountLevel table in order to model this relationship. EF assumes a default FK name as "related entity name+underscore+primary key name" = FeeLevel_LevelId. Because your database table doesn't have this column you get the exception.

EF5 TPH Extra Foreign Keys being generated - How to get rid of them?

This is a sample app I've concocted which produces the same behaviour as my actual much-more-complex app.
I'm obviously missing some configuration aspect somewhere, but can't figure it out for the life of me. For some reason, each collection in my KID class is receiving its' own field and foreign key in the Sweet table....which just isn't necessary as far as I can see.....?
How can I stop EF generating these?
Example classes, config and resulting migration code follows (NB, If I use TPT instead of TPH the extra fields aren't added and OwnerId is used as the relational field just fine)
My classes:
public class Sweet
{
[Key]
public int SweetId { get; set; }
public string SweetName { get; set; }
[ForeignKey("OwnerId")]
public Kid Owner { get; set; }
public int OwnerId { get; set; }
}
public class Chocolate : Sweet {}
public class HardBoiled : Sweet {}
public class Chewy : Sweet {}
public class Kid
{
public int KidId { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Chocolate> Chocolates { get; set; }
public virtual ICollection<HardBoiled> BaggedSweeets { get; set; }
public virtual ICollection<Chewy> PacketSweets { get; set; }
}
My Configuration (Called from OnModelCreating)
public class SweetConfiguration : EntityTypeConfiguration<Sweet>
{
public SweetConfiguration()
{
Map(m => m.ToTable("Sweet"));
Map<Chocolate>(i => i.Requires("SweetType").HasValue(1));
Map<Chewy>(i => i.Requires("SweetType").HasValue(2));
Map<HardBoiled>(f => f.Requires("SweetType").HasValue(3));
}
}
The generated Migration code:
public override void Up()
{
CreateTable(
"dbo.Kid",
c => new
{
KidId = c.Int(nullable: false, identity: true),
FirstName = c.String(),
})
.PrimaryKey(t => t.KidId);
CreateTable(
"dbo.Sweet",
c => new
{
SweetId = c.Int(nullable: false, identity: true),
SweetName = c.String(),
OwnerId = c.Int(nullable: false),
Kid_KidId = c.Int(), <--- DON'T NEED THIS
Kid_KidId1 = c.Int(), <--- OR THIS
Kid_KidId2 = c.Int(), <-- OR THIS!
SweetType = c.Int(),
})
.PrimaryKey(t => t.SweetId)
.ForeignKey("dbo.Kid", t => t.Kid_KidId) <!-- LIKEWISE FOR THESE THREE KEYS
.ForeignKey("dbo.Kid", t => t.Kid_KidId1)
.ForeignKey("dbo.Kid", t => t.Kid_KidId2)
.ForeignKey("dbo.Kid", t => t.OwnerId, cascadeDelete: true)
.Index(t => t.Kid_KidId)
.Index(t => t.Kid_KidId1)
.Index(t => t.Kid_KidId2)
.Index(t => t.OwnerId);
}
UPDATE:
As it would appear my current model isn't supported I have changed my Kid class like this:
public class Kid
{
public int KidId { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Sweet> Sweets { get; set; }
[NotMapped]
public ICollection<HardBoiled> BaggedSweets
{
get
{
return Sweets.OfType<HardBoiled>().ToList();
}
}
... and two more read-only NotMapped properties for the other collections...
}
You cannot use three collections in this model. EF expects that the inverse property and FK property (Owner and OwnerId) are declared directly in the classes the collections are refering to (i.e. in Chocolate, HardBoiled and Chewy) and not in the base class. To make it work and have only a single foreign key you can only define a single navigation collection in Kid that refers to the base class where Owner and OwnerId are declared in:
public class Kid
{
public int KidId { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Sweet> Sweets { get; set; }
}
(You can btw extract specific types from this collection using Sweets.OfType<Chocolate>(), etc.)
This is also the case for TPT. Is it possible that you had no SweetConfiguration and no DbSet<Sweet> in your TPT test? This would result in a model with no inheritance at all (from EF viewpoint) because all properties of the base class Sweet would have been added into the three child tables.

Resources