我有以下SQL表(为了简单起见,删除了列):
create table dbo.Packs
(
Id int identity not null
constraint Packs_Id_PK primary key clustered (Id)
);
create table dbo.Files
(
Id int identity not null
constraint Files_Id_PK primary key clustered (Id),
PackId int not null
);
alter table dbo.Files
add constraint Files_PackId_FK foreign key (PackId) references dbo.Packs(Id) on delete cascade on update cascade;
然后我创建了如下的Pocos:
public class Pack {
public Int32 Id { get; set; }
public virtual ICollection<File> Files { get; set; }
} // Pack
public class File {
public Int32 Id { get; set; }
public int PackId { get; set; }
public virtual Pack Pack { get; set; }
} // File
配置为:
internal class PackMapper : EntityTypeConfiguration<Pack> {
internal PackMapper()
: base() {
ToTable("Packs");
HasKey(x => x.Id);
Property(x => x.Id).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
} // PackMapper
internal class FileMapper : EntityTypeConfiguration<File> {
internal FileMapper()
: base() {
ToTable("Files");
HasKey(x => x.Id);
Property(x => x.Id).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// 1 > CONFIGURATION WITH FK IN ENTITY
Property(x => x.PackId).HasColumnName("PackId").IsRequired();
HasRequired(x => x.Pack).WithMany(x => x.Files).HasForeignKey(x => x.PackId);
// 2 > CONFIGURATION WITHOUT FK IN ENTITY
// HasRequired<Pack>(x => x.Pack).WithMany(y => y.Files).Map(z => { z.MapKey("PackId"); });
}
} // FileMapper
然后我试图删除一个文件:
Pack pack = context.Packs.First(x => x.Id == 31);
IList<Int32> ids = context.Entry<Pack>(pack).Collection(x => x.Files).Query().Select(x => x.Id).ToList();
foreach (int id in ids) {
File file = new File() { Id = id };
context.Files.Attach(file);
context.Files.Remove(file);
}
context.SaveChanges();
如果我使用配置1,文件将被删除。
如果我使用配置2(不需要FK属性),那么我会得到错误:
"Context.Files"中的实体参与"File_Pack"关系。找到了0个相关的"File_Pack_Target"。应为1"File_Pack_Target"。
为什么?在不定义FK特性时,是否需要指定其他内容?
注意:我使用的是EF 5。
定义同一关系的两种方法-一种是使用外键属性和HasForeignKey
,另一种是不使用此类属性和MapKey
-更改外键关联和独立关联之间的关系类型。
使用外键关联,可以通过设置标量属性来指定关系,即外键属性File.PackId
。这个(不可为null)属性总是有一个值,无论您是否显式设置它。至少它有一个默认值0
。使用外键关联时,不需要设置导航属性File.Pack
来告诉EFFile
指的是哪个Pack
。FK属性值就足够了。
另一方面,当使用独立关联时,您的模型没有外键属性,并且告诉EF哪个是与特定File
相关的Pack
的唯一方法是设置导航属性File.Pack
。
您的关系被指定为必需,这也意味着EF希望将导航属性设置为实体,并抱怨null
值。这就是例外的含义。
(当你删除父实体时,不要问我为什么它想要一个相关的实体。我不知道。当只需要向数据库发出父实体的SQLDELETE
语句时,这实际上并不重要。但也许还有更深层次的原因。)
因此,为了使您的代码使用独立关联并消除异常,您需要设置导航属性File.Pack
:
Pack pack = context.Packs.First(x => x.Id == 31);
IList<Int32> ids = context.Entry<Pack>(pack).Collection(x => x.Files).Query()
.Select(x => x.Id).ToList();
foreach (int id in ids) {
File file = new File() { Id = id, Pack = pack };
context.Files.Attach(file);
context.Files.Remove(file);
}
context.SaveChanges();
编辑
BTW:您应该能够使用单个数据库查询,而不是两个查询:
IList<Int32> ids = context.Files.Where(f => f.Pack.Id == 31)
.Select(f => f.Id).ToList();
Pack pack = new Pack { Id = 31 };
foreach (int id in ids) {
File file = new File { Id = id, Pack = pack };
context.Files.Attach(file);
context.Files.Remove(file);
}
context.SaveChanges();
您的约束中有一个删除级联,这就是它删除两个实体的原因:
add constraint Files_PackId_FK foreign key (PackId) references dbo.Packs(Id) on delete cascade on update cascade;