I have the following model in a EF Core 3 project:
public class MemberModel
{
[Key]
public int Id { get; set; }
[Required, MaxLength(255)]
public string Name { get; set; }
[Required, MaxLength(30)]
public string Phone { get; set; }
[Required, MaxLength(255)]
public string Email { get; set; }
[Required, Display(Name="Company")]
public int CompanyId { get; set; }
public bool isDeleted { get; set; }
}
The CompanyId property was added only in March, and a migration, with timestamp 20200226063529 was done:
public partial class addedCompanyIdColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "CompanyId",
table: "Members",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CompanyId",
table: "Members");
}
}
In the interim, I modified the table to add [Required] attribute to the fields, and I didn't apply any migrations.
Today, I added a migration to do update some other table, and the migration code included code that alters the Members table, adding a CompanyId column, and creating a Companies table that was already there:
migrationBuilder.AlterColumn<string>(
name: "Phone",
table: "Members",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(30)",
oldMaxLength: 30,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Members",
maxLength: 255,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(255)",
oldMaxLength: 255,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Email",
table: "Members",
maxLength: 255,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(255)",
oldMaxLength: 255,
oldNullable: true);
migrationBuilder.AddColumn<int>(
name: "CompanyId",
table: "Members",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Companies",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(maxLength: 255, nullable: true),
isDeleted = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Companies", x => x.Id);
});
So the update failed. I had to go to the database to delete the CompanyId column and the Companies table in order to proceed.
What could have caused this issue, and what should I look out for to avoid such outcomes in future?
Is there a requirement that any changes to the models, no matter how trivial, should be followed by a database update before any transactions are performed?
Related
This is a barebones application with only one class: AppUser having only (Id, FirstName, LastName, Phone1, Phone2, KnownAs, EmailAddress, Question, Answer, and IsTrialMode.
There are no other tables!
DataContext is only:
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<AppUser> Users { get; set; }
}
When I do a dotnet ef migrations add command, the migrations file contains:
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FirstName = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Phone1 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Phone2 = table.Column<string>(type: "nvarchar(max)", nullable: true),
KnownAs = table.Column<string>(type: "nvarchar(max)", nullable: true),
EmailAddress = table.Column<string>(type: "nvarchar(max)", nullable: true),
Question = table.Column<string>(type: "nvarchar(max)", nullable: true),
Answer = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsTrialMode = table.Column<bool>(type: "bit", nullable: false),
UserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
NormalizedEmail = table.Column<string>(type: "nvarchar(max)", nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
This is clearly NOT what I was expecting.
What is causing this; how to make it stop?
Thanks in advance.
"Chuck"
From your migration file, the additional properties seem to be the IdentityUser class default properties.
Be sure your AppUser does not extend IdentityUser or IdentityUser<T> class:
public class AppUser
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone1 { get; set; }
public string Phone2 { get; set; }
public string KnownAs { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
public string EmailAddress { get; set; }
public bool IsTrialMode { get; set; }
}
i have model class like below
public class DIMAuthOffice
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Title { get; set; }
[StringLength(50)]
[Required]
public string UserNameManager { get; set; }
public int? ParentID { get; set; }
[ForeignKey(nameof(ParentID))]
public DIMAuthOffice OfficeParent { get; set; }
public List<DIMAuthOffice> OfficeChilds { get; set; } = new List<DIMAuthOffice>();
[ForeignKey(nameof(UserNameManager))]
public DIMAuthUser UserManager { get; set; }
public List<DIMAuthRole> DIMAuthRoles { get; set; } = new List<DIMAuthRole>();
public List<DIMAuthRolePostOffice> DIMAuthRolePostOffices { get; set; } = new List<DIMAuthRolePostOffice>();
}
but when i execute
Add-Migration
the generated File is :
migrationBuilder.CreateTable(
name: "DIMAuthOffices",
columns: table => new
{
ID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: false),
UserNameManager = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
ParentID = table.Column<int>(type: "int", nullable: false),
UserManagerUserName = table.Column<string>(type: "nvarchar(50)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DIMAuthOffices", x => x.ID);
table.ForeignKey(
name: "FK_DIMAuthOffices_DIMAuthOffices_ParentID",
column: x => x.ParentID,
principalTable: "DIMAuthOffices",
principalColumn: "ID");
table.ForeignKey(
name: "FK_DIMAuthOffices_DIMAuthUsers_UserManagerUserName",
column: x => x.UserManagerUserName,
principalTable: "DIMAuthUsers",
principalColumn: "UserName");
});
wrong line is :
ParentID = table.Column(type: "int", nullable: false),
the generated File is Wrong and property ParentID set to nullable:false but this should be true ...
why ???
Doesn't repro for me on EF Core 6. eg
public class DIMAuthOffice
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Title { get; set; }
[StringLength(50)]
[Required]
public string UserNameManager { get; set; }
public int? ParentID { get; set; }
[ForeignKey(nameof(ParentID))]
public DIMAuthOffice OfficeParent { get; set; }
public List<DIMAuthOffice> OfficeChilds { get; set; } = new List<DIMAuthOffice>();
}
creates an initial migration of:
migrationBuilder.CreateTable(
name: "DIMAuthOffice",
columns: table => new
{
ID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserNameManager = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
ParentID = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DIMAuthOffice", x => x.ID);
table.ForeignKey(
name: "FK_DIMAuthOffice_DIMAuthOffice_ParentID",
column: x => x.ParentID,
principalTable: "DIMAuthOffice",
principalColumn: "ID");
});
I use FluentApi and writed this code in modelBuilder and fixed issue
modelBuilder.Entity<DIMAuthOffice>()
.HasOne(q => q.OfficeParent)
.WithMany(q => q.OfficeChilds)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
I have a class for my model and another class for mapping models with EF Core. When I add a migration, EF Core ignores my mapping configuration.
For example, the max length for all properties is max.
Here's my code:
Model class:
public class Product : EntityBase
{
public string Name { get; private set; }
public string Code { get; private set; }
public double UnitPrice { get; private set; }
public bool IsInStock { get; private set; }
public string ShortDescription { get; private set; }
public string Description { get; private set; }
public string Picture { get; private set; }
public string PictureAlt { get; private set; }
public string PictureTitle { get; private set; }
public long CategoryId { get; private set; }
public string Slug { get; private set; }
public string Keywords { get; private set; }
public string MetaDescription { get; private set; }
public ProductCategory Category { get; set; }
}
Mapping class:
public class ProductMapping:IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products");
builder.HasKey(x => x.Id);
builder.Property(x => x.Name).HasMaxLength(255).IsRequired();
builder.Property(x => x.Code).HasMaxLength(15).IsRequired();
builder.Property(x => x.ShortDescription).HasMaxLength(500).IsRequired();
builder.Property(x => x.Picture).HasMaxLength(1000);
builder.Property(x => x.PictureAlt).HasMaxLength(255);
builder.Property(x => x.PictureTitle).HasMaxLength(500);
builder.Property(x => x.Description).HasMaxLength(1000);
builder.Property(x => x.Keywords).HasMaxLength(500).IsRequired();
builder.Property(x => x.MetaDescription).HasMaxLength(150).IsRequired();
builder.Property(x => x.Slug).HasMaxLength(500).IsRequired();
builder.HasOne(x => x.Category)
.WithMany(x => x.Products)
.HasForeignKey(x => x.CategoryId);
}
}
Migration :
public partial class ProductAdd : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Code = table.Column<string>(type: "nvarchar(max)", nullable: true),
UnitPrice = table.Column<double>(type: "float", nullable: false),
IsInStock = table.Column<bool>(type: "bit", nullable: false),
ShortDescription = table.Column<string>(type: "nvarchar(max)", nullable: true),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
Picture = table.Column<string>(type: "nvarchar(max)", nullable: true),
PictureAlt = table.Column<string>(type: "nvarchar(max)", nullable: true),
PictureTitle = table.Column<string>(type: "nvarchar(max)", nullable: true),
CategoryId = table.Column<long>(type: "bigint", nullable: false),
Slug = table.Column<string>(type: "nvarchar(max)", nullable: true),
Keywords = table.Column<string>(type: "nvarchar(max)", nullable: true),
MetaDescription = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
table.ForeignKey(
name: "FK_Products_ProductCategories_CategoryId",
column: x => x.CategoryId,
principalTable: "ProductCategories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Products_CategoryId",
table: "Products",
column: "CategoryId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
}
}
My context:
public class ShopContext:DbContext
{
public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<Product> Products { get; set; }
public ShopContext(DbContextOptions<ShopContext> options):base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var assembly = typeof(ProductCategory).Assembly;
modelBuilder.ApplyConfigurationsFromAssembly(assembly);
base.OnModelCreating(modelBuilder);
}
}
My POCO classes:
[Table]
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
public class CategoryProduct
{
public int CategoryId { get; set; }
public int ProductId { get; set; }
public Category Category { get; set; }
public Product Product { get; set; }
}
[Table]
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
Here's a function for inserting new records:
async Task CreateProduct(Product dto)
{
await ctx.Products.AddAsync(dto);
await ctx.SaveChangesAsync();
}
In dto I pass the following JSON:
{
"name": "Gräff Stettin",
"categoryProducts": [{
"categoryId": 1,
"productId": 1,
"category": {
"id": 1,
"name": "Drinks"
}
}, {
"categoryId": 2,
"productId": 1,
"category": {
"id": 2,
"name": "Alcohol"
}
}
]
}
As a result, at SaveChangesAsync() I get an exception with message regarding attempt to insert already existing Category. Tracing shows the following query:
INSERT INTO "Category" ("Id", "Name") VALUES (#p0, #p1);
How should I change my CreateProduct() method to avoid attempts to add categories with already existing categoryId?
you can use AutoMapper and ignore category =
var config = new MapperConfiguration(cfg => cfg.CreateMap<categoryProductsDto,categoryProducts>())
.ForMember(u => u.Category, options => options.Ignore());
and use mapper =
var mapper = config.CreateMapper();
categoryProducts entity = mapper.Map<categoryProducts>(input);
await _categoryProductsRepository.InsertAndGetIdAsync(entity);
Does anyone know if is there the problem with one to one relation in EF Core and Eager Loading or the problem is my poorly knowledge instead?
For example, if I have one simple one to one relation like this:
public class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Biography Biography { get; set; }
}
public class Biography
{
public int Id { get; set; }
public string BiographyResume { get; set; }
public DateTime DateOfBirth { get; set; }
public string PlaceOfBirth { get; set; }
public string Nationality { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
public class OneDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public OneDbContext(DbContextOptions<OneDbContext> options)
: base(options)
{
}
}
Here is the Migration:
public partial class CreateAuthorsTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
FirstName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Biography",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
BiographyResume = table.Column<string>(nullable: true),
DateOfBirth = table.Column<DateTime>(nullable: false),
PlaceOfBirth = table.Column<string>(nullable: true),
Nationality = table.Column<string>(nullable: true),
AuthorId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Biography", x => x.Id);
table.ForeignKey(
name: "FK_Biography_Authors_AuthorId",
column: x => x.AuthorId,
principalTable: "Authors",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Biography_AuthorId",
table: "Biography",
column: "AuthorId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Biography");
migrationBuilder.DropTable(
name: "Authors");
}
}
And now, in Controller everything is fine without Eager Loading:
[Route("api/[controller]")]
public class AuthorsController : Controller
{
private readonly OneDbContext context;
public AuthorsController(OneDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<IEnumerable<Author>> GetAuthors()
{
return await context.Authors.ToListAsync();
}
}
But when I use Eager Loading it doesn't return anything.
[HttpGet]
public async Task<IEnumerable<Author>> GetAuthors()
{
return await context.Authors.Include(a => a.Biography).ToListAsync();
}
Tested with Postman:
https://localhost:44355/api/authors
for: return await context.Authors.ToListAsync();
It returns:
[
{
"id": 1,
"firstName": "Luka",
"lastName": "Jovic",
"biography": null
},
{
"id": 2,
"firstName": "Stefan",
"lastName": "Savic",
"biography": null
}
]
for: return await context.Authors.Include(a => a.Biography).ToListAsync();
It returns:
Could not get any response
There was an error connecting to https://localhost:44355/api/authors.
Why this might have happened:
The server couldn't send a response:
Ensure that the backend is working properly
Self-signed SSL certificates are being blocked:
Fix this by turning off 'SSL certificate verification' in Settings > General
Proxy configured incorrectly
Ensure that proxy is configured correctly in Settings > Proxy
Request timeout:
Change request timeout in Settings > General