I am using .NET Core 6 with Entity Framework. I have 2 model classes called Category and Subcategory.
Category implements my ICategory interface:
public interface ICategory
{
public int Id { get; set; }
public string Label { get; set; }
public string Value { get; set; }
}
public class Category : ICategory
{
public int Id { get; set; }
public string Label { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
Subcategory inherits from Category and implements my ISubcategory interface:
public interface ISubcategory : ICategory
{
public string InputType { get; set; }
}
public class SubCategory : Category, ISubcategory
{
public string InputType { get; set; } = string.Empty;
public int CategoryId { get; set; }
}
My DbContext looks like this:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Category> Categories { get; set; }
public DbSet<SubCategory> SubCategories { get; set; }
}
When I run
dotnet ef migrations add Migration1
it only generates a table based on the base model, Category:
public partial class Migration1 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Label = table.Column<string>(type: "nvarchar(max)", nullable: false),
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
Discriminator = table.Column<string>(type: "nvarchar(max)", nullable: false),
InputType = table.Column<string>(type: "nvarchar(max)", nullable: true),
CategoryId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Categories");
}
}
I'm expecting the SubCategory table to also be generated. And I noticed that the InputType and CategoryId properties were on the generated table even though those properties only belong to the Subcategory model.
As a test, I created an unrelated model that doesn't inherit from Category. That table did get generated successfully in the migration.
This tells me that my issue is probably related to inheritance. but I'm just not sure what the issue is. Any help is appreciated.
By default Entity Framework generates table per hierarchy. Note, it has Discriminator column that will hold what actual class it corresponds to. You can generate Table per type, but it is not default.
Read more in the docs: https://learn.microsoft.com/en-us/ef/core/modeling/inheritance
Related
I'm trying to create an object with a Guid primary key using [Key], but Entity Framework keeps forcing the creation of a composite key.
public class Challenge
{
[Key]
public Guid Id { get; set; }
public Guid ChallengerId { get; set; }
public Guid ChallengeeId { get; set; }
[ForeignKey("ChallengerId")]
public virtual Player Challenger { get; set; }
[ForeignKey("ChallengeeId")]
public virtual Player Challengee { get; set; }
public DateTime Initiated { get; set; }
}
and its migration...
migrationBuilder.CreateTable(
name: "Challenges",
columns: table => new
{
ChallengerId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ChallengeeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Initiated = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Challenges", x => new { x.ChallengerId, x.ChallengeeId });
table.ForeignKey(
name: "FK_Challenges_Players_ChallengeeId",
column: x => x.ChallengeeId,
principalTable: "Players",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Challenges_Players_ChallengerId",
column: x => x.ChallengerId,
principalTable: "Players",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
Note that I've created the Player class using the same methodology, and Entity Framework respects the [Key] attribute there.
public class Player
{
[Key]
public Guid Id { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual IdentityUser User { get; set; }
public virtual ICollection<Game> Games { get; set; }
[InverseProperty("Challenger")]
public virtual ICollection<Challenge> OutgoingChallenges { get; set; }
[InverseProperty("Challengee")]
public virtual ICollection<Challenge> IncomingChallenges { get; set; }
}
migrationBuilder.CreateTable(
name: "Players",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Players", x => x.Id);
table.ForeignKey(
name: "FK_Players_IdentityUser_UserId",
column: x => x.UserId,
principalTable: "IdentityUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Even when I go into the migration and change
table.PrimaryKey("PK_Challenges", x => new { x.ChallengerId, x.ChallengeeId });
to
table.PrimaryKey("PK_Challenges", x => x.Id);
Entity Framework seems to be struggling, as it creates Challenges in the SQL database with a composite key.
I'm using .NET 6.
Might you be missing [Key] attribute in your Player model?
public class Player
{
//Here should be a [Key] annotation
public Guid Id { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual IdentityUser User { get; set; }
public virtual ICollection<Game> Games { get; set; }
[InverseProperty("Challenger")]
public virtual ICollection<Challenge> OutgoingChallenges { get; set; }
[InverseProperty("Challengee")]
public virtual ICollection<Challenge> IncomingChallenges { get; set; }
}
I found the guilty culprit: AppDbContext.cs
modelBuilder.Entity<Challenge>()
.HasForeignKey(c => new { c.ChallengerId, c.ChallengeeId });
I'm trying to configure EF to include documents when retriving a user or product. The entity Document has a ReferenceId property which should store either UserId or ProductId. This way, when I save a document for a user or product, the UserId or ProductId is saved to Document.ReferenceId.
Entities:
public class User
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Product
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Document
{
public string Id { get; set; }
public string ReferenceId { get; set; }
}
Configuring:
builder.Entity<User>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Product>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Document>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.ToTable("Documents");
});
Saving:
var user = new User { };
var userDocument = new Document { ReferenceId = user.Id };
var product = new Product { };
var productDocument = new Document { ReferenceId = product.Id };
_context.Users.Add(user);
_context.Products.Add(product);
_context.Add(userDocument);
_context.Add(productDocument);
_context.SaveChanges();
Migrations:
migrationBuilder.CreateTable(
name: "Documents",
columns: table => new
{
Id = table.Column<string>(nullable: false),
ReferenceId = table.Column<string>(nullable: true),
ProductId = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Documents", x => x.Id);
table.ForeignKey(
name: "FK_Documents_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Documents_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
I don't want 2 foreign keys (ProductId and UserId) to be created on Documents table. Is there a way to make EF automatically link UserId and ProductId to ReferenceId?
The proper way to solve it would be to have User and Product inherit a base class and move the Id and Documents properties to that class.
public class BaseObject
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public string BaseObjectId { get; set; }
}
The only way I see, is to use TPH inheritance (See here for more information).
I have quoted and edited the answer by Erik H.
public enum DocumentType
{
User = 0,
Product = 1
}
public class BaseObject
{
public string Id { get; set; }
public ObjectType DocumentType{ get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public int Id { get; set; }
public string BaseObjectId { get; set; }
public virtual BaseObject DocumentObject { get; set; }
}
Via fluent-Api you can set a discriminator. This way ef core will only create one table for for both objects Product and User and distinguishes their type by the value of the discriminator column. But only as long as they have exactly the same properties which they share from the base class. As soon as you add properties to one of those subclasses a new table will be created (with all properties from the base- and subclass).
Here is the configuration for the discriminator:
modelBuilder.Entity<BaseObject>()
.HasDiscriminator<DocumentType>("DocumentType")
.HasValue<User>(DocumentType.User)
.HasValue<Product>(DocumentType.Product)
This may not be a clean approach (for me it seems like User and Product should not inherit from the same base class, because they do not share anything than the relations to documents). But it should work as you want it.
You can create a many to many table:
public class Product
{
public string Id { get; set; }
public ICollection<ProductDocument> ProductDocuments{ get; set; }
}
public class Document
{
public string ReferenceId { get; set; }
}
public class ProductDocument
{
public ICollection<Product> Products{ get; set; }
public ICollection<Document> Documents { get; set; }
}
You will have to create a separate table for your user table ie UserDocumentes using the same pattern.
After update my model and add data annotation.
Then I executed add-migration and update-database commands and I got the following error:
Column names in each table must be unique. Column name 'LastName' in
table 'HumanCustomers' is specified more than once.
But LastName field once used.
HumanCustomer Class:
public class HumanCustomer
{
public int Id { get; set; }
public int UserId { get; set; }
[Required]
[MinLength(2)]
[MaxLength(20)]
public string Name
{
get => Name;
set
{
value.TrimAndReduce();
}
}
[Required]
[MinLength(2)]
[MaxLength(20)]
public string LastName
{
get => LastName;
set
{
value = value.TrimAndReduce();
}
}
[NotMapped]
public string FullName
{
get { return Name + LastName; }
}
[Required(AllowEmptyStrings = true)]
public int GenderId { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public DateTime BirthDate { get; set; }
public int IdentityTypeId { get; set; }
public string IdentityCode { get; set; }
[Required]
[ForeignKey("UserId")]
public virtual User User { get; set; }
[Required]
[ForeignKey("IdentityTypeId")]
public virtual IdentityType IdentityType { get; set; }
[Required]
[ForeignKey("GenderId")]
public virtual Gender Gender { get; set; }
}
and migration:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "LastName",
table: "HumanCustomers",
maxLength: 20,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Name",
table: "HumanCustomers",
maxLength: 20,
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LastName",
table: "HumanCustomers");
migrationBuilder.DropColumn(
name: "Name",
table: "HumanCustomers");
}
The error is clearly indicating that you already have a column called LastName in your table and the migration cannot be applied to add a new column with the same name.
Either go to the database management system and remove the column manually, or if you have just switched to code-first and you have existing schema that you want to ignore in the next migration then delete the migration file and create a new one using -IgnoreChanges flag:
Add-Migration migrationName –IgnoreChanges
Though, as #Ahmad commented, -IgnoreChanges is not supported in EF Core, therefore, one thing you can do is that if you don't want to bother deleting the column from the table manually, you can comment out the Add LastName code in the Up method.
I'm setting up a TPH inheritance in C# MVC for the first time, and I'm a bit confused on how to map subclass values to the table. For example, my parent entity's fluent map is declared using:
public class ProjectTaskMap : EntityTypeConfiguration<ProjectTask>
I have individual maps set up using:
Map<ProjectTL>(m => m.Requires("Type").HasValue("TL"));
As a further example: One of the subclasses needs to have a many-to-many mapping. I don't know how to configure that with TPH since I can't access the child class's properties to declare the mapping.
I can't access the subclass properties in this parent map, however (since it's calling the config of ProjectTask). I can't specify how to map a field to the table, I can't do anything with them.
What am I missing to be able to do this? This is an old system that was just upgraded to EF 6.1, so I don't have EF Designer or anything like that, only these fluent mappings.
Parent class:
public class ProjectTask : BaseEntity
{
public virtual int ProjectId { get; set; }
// various other properties
public virtual Project Project { get; set; }
public virtual ICollection<ProjectTaskFile> Files { get; set; }
}
Two child classes have none of their own properties (they're empty shells), but the third does. ECOs is part of a many-to-many relationship.
public class ProjectET : ProjectTask
{
public virtual int SalesOrderId { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
public ICollection<EngChangeOrders> ECOs { get; set; }
}
When creating your model with Code First, TPH is the default strategy for the types that participate in the inheritance hierarchy. Take a look at the following structure:
public class Project
{
public int ProjectId { get; set; }
}
public class ProjectTask
{
public int ProjectTaskId { get; set; }
public virtual Project Project { get; set; }
public string SomeString { get; set; }
}
public class ProjectET : ProjectTask
{
public ICollection<Order> ECOs { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string SomeString { get; set; }
}
Mapping:
public class Context : DbContext
{
public Context() : base("Model2")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectET> ProjectETs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasKey(i => i.ProjectId);
modelBuilder.Entity<ProjectTask>()
.HasKey(i => i.ProjectTaskId);
base.OnModelCreating(modelBuilder);
}
}
Migrations Generated
CreateTable(
"dbo.ProjectTasks",
c => new
{
ProjectTaskId = c.Int(nullable: false, identity: true),
SomeString = c.String(),
Discriminator = c.String(nullable: false, maxLength: 128),
Project_ProjectId = c.Int(),
})
.PrimaryKey(t => t.ProjectTaskId)
.ForeignKey("dbo.Projects", t => t.Project_ProjectId)
.Index(t => t.Project_ProjectId);
CreateTable(
"dbo.Orders",
c => new
{
OrderId = c.Int(nullable: false, identity: true),
SomeString = c.String(),
ProjectET_ProjectTaskId = c.Int(),
})
.PrimaryKey(t => t.OrderId)
.ForeignKey("dbo.ProjectTasks", t => t.ProjectET_ProjectTaskId)
.Index(t => t.ProjectET_ProjectTaskId);
CreateTable(
"dbo.Projects",
c => new
{
ProjectId = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.ProjectId);
Does that work for you?
Your fluent config can obviously only address a single entity type at a time, but there's nothing stopping you from adding fluent config for your subclass as well. Not sure what the complication is here.
public class ProjectETMap : EntityTypeConfiguration<ProjectET>
{
public ProjectETMap()
{
HasMany(m => m.ECOs).WithMany();
}
}
I have the following EF code-first entity and its corresponding configuration:
public class Category
{
public virtual long Id { get; set; }
public Guid Guid { get; set; }
public string Name { get; set; }
}
public class CategoryConfiguration:
EntityTypeConfiguration<Category>
{
public CategoryConfiguration ()
{
this.HasKey(entity => entity.Id);
this.Property(entity => entity.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(entity => entity.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
Setting the DatabaseGeneratedOption.Identity option on the field Guid seems to have no effect and the corresponding table in SQL Server has the IDENTITY option disabled.
Is there a way to have a GUID/UNIQUEIDENTIFIER column in EF that is auto-generated but at the same time NOT part of the primary key?
EF doesn't "auto-generate" anything related to the ID, that's all handled by the database. Also DatabaseGeneratedOption.Identity isn't tied to the primary key, it's just an indicator to EF that the field value is generated by the DB and therefore should be pulled down.
As long as the default value in the database for your field is set to newid() or newsequentialid() then DatabaseGeneratedOption.Identity should work.
a hack...
public calss Guid1 {
public Guid Guid { get; set; }
}
public class Category {
public virtual long Id { get; set; }
public Guid1 Guid { get; set; }
public string Name { get; set; }
}
public class CategoryConfiguration:EntityTypeConfiguration<Category> {
public CategoryConfiguration () {
HasKey(entity => entity.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasReqired(x => x.Guid1);
}
}
public class Guid1Configuration:EntityTypeConfiguration<Guid1> {
public Guid1Configuration () {
HasKey(x => x.Guid);
Property(x => x.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
this should work but you will get a useless table Guid1.
For the benefit of searchers using EntityFramworkCore (.NET core), you can do the following:
EF Model (notice default value set on creation)
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public Guid BlogGuid { get; set; } = Guid.NewGuid();
public List<Post> Posts { get; set; }
}
Create the migration, then change the default to defaultValueSql: "newid()":
public partial class BlogGuidTest : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "BlogGuid",
table: "Blogs",
nullable: false,
defaultValueSql: "newid()");
// Add contraint to be safe
migrationBuilder.AddUniqueConstraint("BlogGuid_Unique", "Blogs", "BlogGuid");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint("BlogGuid_Unique", "Blogs");
migrationBuilder.DropColumn(
name: "BlogGuid",
table: "Blogs");
}
}
Then update the database.
Seems to work ok for me.