EF Code First: Migrations removing needed columns and adding unwanted ones - ef-code-first

Note: there is a "better" iteration of this question here:
EF Code First migrations: Table Per Hierarchy Bug
I am trying to figure out the exact steps need to take an existing database and overlay entity framework code first with a Table Per Hierarchy structure. However no matter what I do, the end result migration is "wrong".
Here is the sample database, exact steps and output.
Existing database structure
CREATE TABLE [dbo].[Categories](
[Id] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Name] [nvarchar](100) NOT NULL,
)
GO
CREATE TABLE [dbo].[A](
[Id] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Discriminator] [nvarchar](20) NOT NULL,
[Description] [nvarchar](100) NOT NULL,
[CategoryId] [int] NULL
)GO
ALTER TABLE [dbo].[A]
WITH CHECK ADD CONSTRAINT [FK_dbo.A_dbo.Categories_CategoryId]
FOREIGN KEY([CategoryId])
REFERENCES [dbo].[Categories] ([Id])
GO
ALTER TABLE [dbo].[A]
CHECK CONSTRAINT [FK_dbo.A_dbo.Categories_CategoryId]
GO
"Reverse Engineer Code First" generates the following:
public partial class EntityContext : DbContext {
static EntityContext() {
Database.SetInitializer<EntityContext>(null);
}
public EntityContext()
: base("Name=Test47Context") {
}
public DbSet<A> A { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new AMap());
modelBuilder.Configurations.Add(new CategoryMap());
}
}
public partial class A {
public int Id { get; set; }
public string Discriminator { get; set; }
public string Description { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public partial class Category {
public Category() {
this.A = new List<A>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> A { get; set; }
}
public class AMap : EntityTypeConfiguration<A> {
public AMap() {
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Discriminator)
.IsRequired()
.HasMaxLength(20);
this.Property(t => t.Description)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("A");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Discriminator).HasColumnName("Discriminator");
this.Property(t => t.Description).HasColumnName("Description");
this.Property(t => t.CategoryId).HasColumnName("CategoryId");
// Relationships
this.HasOptional(t => t.Category)
.WithMany(t => t.A)
.HasForeignKey(d => d.CategoryId);
}
}
public class CategoryMap : EntityTypeConfiguration<Category> {
public CategoryMap() {
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("Categories");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
}
}
Implement TPH, by
Adding Derived.cs inherits from A.cs
Moving Category and CategoryId properties from A.cs to Derived.cs
Remove Discriminator property from A.cs
In AMap.cs remove Discriminator, CategoryId and Category Foreign Key mapping.
Adding TPH mapping to OnModelCreating
public partial class EntityContext : DbContext {
static EntityContext() {
Database.SetInitializer<EntityContext>(null);
}
public EntityContext()
: base("Name=Test47Context") {
}
public DbSet<A> A { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new AMap());
modelBuilder.Configurations.Add(new CategoryMap());
modelBuilder.Entity<A>()
.Map<Derived>(x => x.Requires("Discriminator").HasValue("Derived"));
}
}
public partial class A {
public int Id { get; set; }
public string Description { get; set; }
}
public class Derived : A {
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public partial class Category {
public Category() {
this.A = new List<A>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> A { get; set; }
}
public class AMap : EntityTypeConfiguration<A> {
public AMap() {
// Primary Key
this.HasKey(t => t.Id);
this.Property(t => t.Description)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("A");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Description).HasColumnName("Description");
}
}
public class CategoryMap : EntityTypeConfiguration<Category> {
public CategoryMap() {
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("Categories");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
}
}
"Add-migration TPH" generates the following
public partial class TPH : DbMigration
{
public override void Up()
{
AddColumn("dbo.A", "Category_Id", c => c.Int());
AddForeignKey("dbo.A", "Category_Id", "dbo.Categories", "Id");
CreateIndex("dbo.A", "Category_Id");
DropColumn("dbo.A", "Discriminator");
}
public override void Down()
{
AddColumn("dbo.A", "Discriminator", c => c.String(nullable: false, maxLength: 20));
DropIndex("dbo.A", new[] { "Category_Id" });
DropForeignKey("dbo.A", "Category_Id", "dbo.Categories");
DropColumn("dbo.A", "Category_Id");
}
}
Why does it drop the Discriminator column instead of just altering it to nvarchar(128)?
Why doesn't just use the existing CategoryId column instead of adding Category_Id?

Related

Introducing FOREIGN KEY constraint 'FK_Product_User_UserId' on table 'Product' may cause cycles or multiple cascade paths

i cannot create database with ef core
error : Introducing FOREIGN KEY constraint 'FK_Product_User_UserId' on table 'Product' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
this is my product class
{
public Product()
{
}
public Guid UserId { get; set; }
public Guid CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string PhotoPath { get; set; }
public decimal Price { get; set; }
public Category Category { get; set; }
public User User { get; set; }
}
and
this is my user class
{
public User()
{
Payments = new HashSet<Payment>();
Categories = new HashSet<Category>();
Products = new HashSet<Product>();
}
public string Username { get; set; }
public Guid Password { get; set; }
public ICollection<Payment> Payments { get; set; }
public ICollection<Category> Categories { get; set; }
public ICollection<Product> Products { get; set; }
}
its mapping class
{
public ProductMap()
{
}
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.UserId).IsRequired();
builder.Property(x => x.CreatedDate).IsRequired();
builder.Property(x => x.Description).HasMaxLength(500);
builder.Property(x => x.IsActive).IsRequired();
builder.Property(x => x.Name).HasMaxLength(500).IsRequired();
builder.Property(x => x.PhotoPath).HasMaxLength(4000);
builder.Property(x => x.Price).HasColumnType("decimal(10,3)").IsRequired();
builder.HasOne(x => x.Category).WithMany(x => x.Products).HasForeignKey(x => x.CategoryId);
builder.HasOne(x => x.User).WithMany(x => x.Products).HasForeignKey(x => x.UserId);
}
}
and i cannot create database cause error like this posts title.
what can i do?
Thanks.
You are saying that each user has many Categories and each Category has many Products so you have to remove this line from the User class since it causing cycle path
public ICollection<Product> Products { get; set; }
And also fix the last line of the Configure method:
builder.HasOne(x => x.User).WithMany(x => x.Categories).HasForeignKey(x => x.UserId);

EF table splitting with table AspNetUsers

I use two classes.
public partial class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<IntRole> Roles { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
public partial class ApplicationUser : IdentityUser<int, IntUserLogin, IntUserRole, IntUserClaim>
{
public virtual User User { get; set; }
}
I want to map both classes to table AspNetUsers.
I configured all fields in this table to make them optional.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().HasOptional(a => a.User).WithMany().HasForeignKey(a => a.Id);
modelBuilder.Entity<ApplicationUser>().HasRequired(a => a.User).WithRequiredDependent(s => s.ApplicationUser);
modelBuilder.Entity<ApplicationUser>().Property(a => a.EmailConfirmed).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.PhoneNumber).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.PhoneNumberConfirmed).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.TwoFactorEnabled).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.LockoutEnabled).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.AccessFailedCount).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.UserName).IsOptional();
modelBuilder.Entity<User>().ToTable("AspNetUsers");
}
I make automatic migration and the result is successful.
When I want to add single User defined by class User in controller.
context.SpecialUser.Add(new Models.User { FirstName = "Halo", LastName = "Halo2" });
context.SaveChanges();
I got the following info.
Invalid data encountered. A required relationship is missing. Examine StateEntries to determine the source of the constraint violation.
What relationship should I define?
Result of InnerException.

Validation failed for one or more entities. UserManager.AddToRole()

I'm developing a Web Api using ASP.NET MVC5 and I have successfully implemented a Custom IdentityUser.Like So
public partial class Authentication : IdentityUser
{
public Authentication()
{
this.LoginSessions = new List<LoginSession>();
}
[Required]
public string PersonId { get; set; }
public virtual Lecturer Lecturer { get; set; }
public virtual Student Student { get; set; }
public virtual ICollection<LoginSession> LoginSessions { get; set; }
}
with the following Maps
public class IdentityUserLoginConfiguration: EntityTypeConfiguration<IdentityUserLogin>
{
public IdentityUserLoginConfiguration()
{
HasKey(t => t.UserId);
}
}
public class IdentityRoleConfiguration : EntityTypeConfiguration<IdentityRole>
{
public IdentityRoleConfiguration()
{
HasKey(t => t.Id);
}
}
public class AuthenticationMap : EntityTypeConfiguration<Authentication>
{
public AuthenticationMap()
{
// Primary Key
this.HasKey(t => t.PersonId);
// Properties
this.Property(t => t.PersonId)
.IsRequired()
.HasMaxLength(14);
// Table & Column Mappings
this.ToTable("Authentication");
this.Property(t => t.PersonId).HasColumnName("PersonID");
// Relationships
this.HasOptional(t => t.Lecturer)
.WithOptionalPrincipal(t => t.Authentication);
this.HasOptional(t => t.Student)
.WithOptionalPrincipal(t => t.Authentication);
}
}
But now my problem is that when I'm adding a user to a Role Using the
UserManager.AddToRole()
method I get a DBValidationError
This is really worrying me and would like some help as soon as possible

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.

Entity Framework - Multiple Many-To-Many Relationships

I am trying to describe the mapping to EF to create multiple many-to-many relationships between the following two entities (simplified):
Product:
public class Product
{
[Key]
public int ProductID { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
public virtual ICollection<Transaction> RefundTransactions { get; set; }
public virtual ICollection<Transaction> VoidTransactions { get; set; }
}
Transaction:
public class Transaction
{
[Key]
public int TransactionID { get; set; }
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<Product> RefundProducts { get; set; }
public virtual ICollection<Product> VoidProducts { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Transaction>()
.HasMany(m => m.Products)
.WithMany(t => t.Transactions)
.Map(m =>
{
m.ToTable("Transaction_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("ProductID");
}
);
modelBuilder.Entity<Transaction>()
.HasMany(transaction => transaction.VoidProducts)
.WithMany(t => t.VoidTransactions)
.Map(m =>
{
m.ToTable("Transaction_Void_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("VoidProductID");
}
);
modelBuilder.Entity<Transaction>()
.HasMany(m => m.RefundProducts)
.WithMany(t => t.Transactions)
.Map(m =>
{
m.ToTable("Transaction_Refund_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("RefundProductID");
}
);
Exception:
Type Transaction_Products is not defined in namespace Nautix_EPOS.Models
Now, I think this may be because I am defining the mapping 3 times separately. And possibly overwriting the first with the second, and the second with the last.
Question:
How can I tell EF about the multiple many-to-many mappings between the same two tables?
I figured it out, it's because I used the same t.Transactions in the first and third declaration. I should have use t.RefundTransactions:
modelBuilder.Entity<Transaction>()
.HasMany(m => m.Products)
.WithMany(t => t.Transactions)
.Map(m =>
{
m.ToTable("Transaction_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("ProductID");
}
);
modelBuilder.Entity<Transaction>()
.HasMany(transaction => transaction.VoidProducts)
.WithMany(t => t.VoidTransactions)
.Map(m =>
{
m.ToTable("Transaction_Void_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("VoidProductID");
}
);
modelBuilder.Entity<Transaction>()
.HasMany(m => m.RefundProducts)
.WithMany(t => t.RefundTransactions)
.Map(m =>
{
m.ToTable("Transaction_Refund_Product_Mapping");
m.MapLeftKey("TransactionID");
m.MapRightKey("RefundProductID");
}
);

Resources