EF Code First 一对一和一对多映射
EF Code First One-to-One AND One-to-Many mapping
我有以下模型,我正在尝试从相同的父子实体构建一对一和一对多的关系。一对多关系适用于我当前的映射,但我正在努力添加新的一对一关系(对于 CoverPicture 属性)。以下是相关模型和 EF 映射代码:
Category.cs:
public int Id { get; set; }
public string Name { get; set; }
public Guid? CoverPictureId { get; set; }
public virtual Picture CoverPicture { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
Picture.cs:
public Guid Id { get; set; }
public string FileName { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
相关类别 EntityTypeConfiguration<Category>
映射(不正确):
this.HasOptional(t => t.CoverPicture)
.WithRequired()
.Map(m => m.MapKey("CoverPictureId"))
.WillCascadeOnDelete(false);
相关图片EntityTypeConfiguration<Picture>
映射(正确):
this.HasRequired(t => t.Category)
.WithMany(t => t.Pictures)
.HasForeignKey(k => k.CategoryId);
当我尝试为新 CoverPicture
属性 添加迁移时,EF 尝试将 CoverPictureId
列添加到 Category
table (这是我想要的)而且 CoverPictureId
到 Picture
table(这不是我想要的;Picture
已经定义和映射了一个键)。
这是由 EF 构建的 Up()
迁移代码:
AddColumn("dbo.Category", "CoverPictureId", c => c.Guid());
AddColumn("dbo.Picture", "CoverPictureId", c => c.Int());
CreateIndex("dbo.Picture", "CoverPictureId");
AddForeignKey("dbo.Picture", "CoverPictureId", "dbo.Category", "Id");
我做错了什么?
要解决您的问题,请尝试映射该关系,如下所示:
this.HasOptional(p => p.CoverPicture)
.WithMany().HasForeignKey(p=>p.CoverPictureId)
.WillCascadeOnDelete(false);
在一对一关系中,一端必须是 主体,第二端必须是 从属。当你配置这种关系时,Entity Framework要求依赖的主键也是外键。根据您的配置,主体为 Category
,依赖项为 Picture
。这就是 FK CoverPictureId
是 int
而不是 Guid
,因为这确实是您的迁移代码解释的 Category
PK。
我不确定您使用的是哪个版本的 EF,但较新的版本不允许您执行您尝试执行的映射类型,您将收到以下错误:
The navigation property 'Pictures' declared on type 'YourProject.Category' has been configured with conflicting multiplicities.
那是为什么呢?让我们看看您的映射,以及您用简单的英语告诉我们的内容 Entity Framework:
this.HasRequired(t => t.Category) //A Picture must have 1 Category
.WithMany(t => t.Pictures) //A Category can have many Pictures
this.HasOptional(t => t.CoverPicture) //A Category may or may not have 1 Picture
.WithRequired() //A Picture must have 1 Category
将其与 octavioccl 的答案进行比较:
this.HasOptional(p => p.CoverPicture) //A Category may or may not have 1 Picture
.WithMany() //A Picture can have many Categories
从 WithRequired 到 WithMany 的变化是交换放置外键的位置。现在你有了你正在寻找的映射......有点像:
CreateTable(
"dbo.Categories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
CoverPicture_Id = c.Guid(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Pictures", t => t.CoverPicture_Id)
.Index(t => t.CoverPicture_Id);
CreateTable(
"dbo.Pictures",
c => new
{
Id = c.Guid(nullable: false),
FileName = c.String(),
Category_Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Categories", t => t.Category_Id, cascadeDelete: true)
.Index(t => t.Category_Id);
但让我们停下来想一想。您不仅基本上在两个方向上定义了一对多关系(不要与多对多混淆),而且还破坏了模型的完整性。现在您可以将 Picture
指定为 Category
的 CoverPicture
,即使 Picture
不属于 Category
。那不是你想要的,最终会让你头疼。与其在 Category
上显式定义一个 CoverPicture
属性,不如这样呢?
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
public SetCoverPicture(Picture picture)
{
if(!Pictures.Contains(picture))
{
throw new ArgumentException("Picture is not in this Category");
}
var currentCoverPicture = Pictures.FirstOrDefault(p=p.IsCoverPicture == true);
if(currentCoverPictur e!= null)
{
currentCoverPicture.IsCoverPicture = false;
}
picture.IsCoverPicture = true;
}
}
public class Picture
{
public Guid Id { get; set; }
public string FileName { get; set; }
public int Category_Id { get; set; }
public virtual Category Category { get; set; }
public bool IsCoverPicture { get; protected internal set; }
}
这会强制您的不变量(业务规则)表明...
- a
CoverPicture
for a Category
必须属于那个 Category
(由数据库强制执行)和
- 只能有1个
CoverPicture
对于 Category
(在代码中强制执行)
您可以使用 octavioccl 提供的代码执行类似的操作,但此处提供的代码会产生更清晰、更易于理解的物理数据模型。
我有以下模型,我正在尝试从相同的父子实体构建一对一和一对多的关系。一对多关系适用于我当前的映射,但我正在努力添加新的一对一关系(对于 CoverPicture 属性)。以下是相关模型和 EF 映射代码:
Category.cs:
public int Id { get; set; }
public string Name { get; set; }
public Guid? CoverPictureId { get; set; }
public virtual Picture CoverPicture { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
Picture.cs:
public Guid Id { get; set; }
public string FileName { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
相关类别 EntityTypeConfiguration<Category>
映射(不正确):
this.HasOptional(t => t.CoverPicture)
.WithRequired()
.Map(m => m.MapKey("CoverPictureId"))
.WillCascadeOnDelete(false);
相关图片EntityTypeConfiguration<Picture>
映射(正确):
this.HasRequired(t => t.Category)
.WithMany(t => t.Pictures)
.HasForeignKey(k => k.CategoryId);
当我尝试为新 CoverPicture
属性 添加迁移时,EF 尝试将 CoverPictureId
列添加到 Category
table (这是我想要的)而且 CoverPictureId
到 Picture
table(这不是我想要的;Picture
已经定义和映射了一个键)。
这是由 EF 构建的 Up()
迁移代码:
AddColumn("dbo.Category", "CoverPictureId", c => c.Guid());
AddColumn("dbo.Picture", "CoverPictureId", c => c.Int());
CreateIndex("dbo.Picture", "CoverPictureId");
AddForeignKey("dbo.Picture", "CoverPictureId", "dbo.Category", "Id");
我做错了什么?
要解决您的问题,请尝试映射该关系,如下所示:
this.HasOptional(p => p.CoverPicture)
.WithMany().HasForeignKey(p=>p.CoverPictureId)
.WillCascadeOnDelete(false);
在一对一关系中,一端必须是 主体,第二端必须是 从属。当你配置这种关系时,Entity Framework要求依赖的主键也是外键。根据您的配置,主体为 Category
,依赖项为 Picture
。这就是 FK CoverPictureId
是 int
而不是 Guid
,因为这确实是您的迁移代码解释的 Category
PK。
我不确定您使用的是哪个版本的 EF,但较新的版本不允许您执行您尝试执行的映射类型,您将收到以下错误:
The navigation property 'Pictures' declared on type 'YourProject.Category' has been configured with conflicting multiplicities.
那是为什么呢?让我们看看您的映射,以及您用简单的英语告诉我们的内容 Entity Framework:
this.HasRequired(t => t.Category) //A Picture must have 1 Category
.WithMany(t => t.Pictures) //A Category can have many Pictures
this.HasOptional(t => t.CoverPicture) //A Category may or may not have 1 Picture
.WithRequired() //A Picture must have 1 Category
将其与 octavioccl 的答案进行比较:
this.HasOptional(p => p.CoverPicture) //A Category may or may not have 1 Picture
.WithMany() //A Picture can have many Categories
从 WithRequired 到 WithMany 的变化是交换放置外键的位置。现在你有了你正在寻找的映射......有点像:
CreateTable(
"dbo.Categories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
CoverPicture_Id = c.Guid(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Pictures", t => t.CoverPicture_Id)
.Index(t => t.CoverPicture_Id);
CreateTable(
"dbo.Pictures",
c => new
{
Id = c.Guid(nullable: false),
FileName = c.String(),
Category_Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Categories", t => t.Category_Id, cascadeDelete: true)
.Index(t => t.Category_Id);
但让我们停下来想一想。您不仅基本上在两个方向上定义了一对多关系(不要与多对多混淆),而且还破坏了模型的完整性。现在您可以将 Picture
指定为 Category
的 CoverPicture
,即使 Picture
不属于 Category
。那不是你想要的,最终会让你头疼。与其在 Category
上显式定义一个 CoverPicture
属性,不如这样呢?
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
public SetCoverPicture(Picture picture)
{
if(!Pictures.Contains(picture))
{
throw new ArgumentException("Picture is not in this Category");
}
var currentCoverPicture = Pictures.FirstOrDefault(p=p.IsCoverPicture == true);
if(currentCoverPictur e!= null)
{
currentCoverPicture.IsCoverPicture = false;
}
picture.IsCoverPicture = true;
}
}
public class Picture
{
public Guid Id { get; set; }
public string FileName { get; set; }
public int Category_Id { get; set; }
public virtual Category Category { get; set; }
public bool IsCoverPicture { get; protected internal set; }
}
这会强制您的不变量(业务规则)表明...
- a
CoverPicture
for aCategory
必须属于那个Category
(由数据库强制执行)和 - 只能有1个
CoverPicture
对于Category
(在代码中强制执行)
您可以使用 octavioccl 提供的代码执行类似的操作,但此处提供的代码会产生更清晰、更易于理解的物理数据模型。