当使用Entity Framework 6在不使用外键的情况下将具有唯一约束的一对多关系的实体插入到SQLite数据库中时,我遇到了一个问题,并且我无法**改变这一点。让我们考虑这个例子:
型号
public class Box {
public int BoxId { get; set; }
public ICollection<Item> Items{ get; set; }
}
public class Item {
public int ItemId { get; set; }
public Box Box { get; set; }
}
字符串
第一次插入带有新框的项目
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
db.Items.Add(item);
db.SaveChanges();
}
型
这将工作并创建Item和Box。
如果我再次运行与之前相同的代码,我会得到一个“unique constraint fail”错误,因为它不能创建另一个ID = 1的Box。所以解决方案是:
验证框
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
var box = db.Box.FirstOrDefault(x => x.Id == box.Id);
item.Box = box ?? item.Box;
db.Items.Add(item);
db.SaveChanges();
}
型
我真的总是需要从数据库中获取盒子吗?如果是,我可以将对象保存在缓存中并重用它吗?我如何抽象获取盒子?我可以创建一个BoxService并在内部打开一个新的作用域上下文来获取盒子吗?
我在这里或多或少地添加了上下文和迁移,因为我的真实的场景涉及更多的属性,但为了简单起见,我只展示了这些模型。
移民
migrationBuilder.CreateTable(
name: "Boxs",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Boxs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Items",
columns: table => new
{
ItemId = table.Column<string>(type: "TEXT", nullable: false)
BoxId = table.Column<string>(type: "TEXT", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Items", x => x.ItemId);
table.ForeignKey(
name: "FK_Items_Boxs_BoxId",
column: x => x.BoxId,
principalTable: "Boxs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
型
背景
public class ItemsDbContext : DbContext
{
public ItemsDbContext(DbContextOptions<ItemsDbContext> options) : base(options) { }
public DbSet<Boxs> Boxs { get; set; }
public DbSet<Items> Items { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Boxs>().HasMany(s => s.Items).WithOne(s => s.Box);
}
}
型
1条答案
按热度按时间gywdnpxw1#
我真的总是需要从数据库中获取盒子吗?如果是,我可以将对象保存在缓存中并重用它吗?
不,你不需要,但你通常需要。这是一个关联问题。你想创建一个新的项目,但将其与现有的Box相关联。EF无法知道它所传递的Box引用是代表现有的Box还是新的Box,除非你告诉它。要在不获取Box的情况下执行您正在查找的操作:
字符串
这在本例中是可行的,因为我们定义了一个新的DbContext,它什么也不跟踪,只处理一个操作。如果你有一个类似于注入的DbContext示例,它 * 可能 * 已经跟踪了desured box(在另一个操作中加载,或被另一个项目引用等),那么你需要更多的工作来确保操作是安全的:
型
第一件事是检查本地跟踪缓存中是否存在Box。这不会转到数据库,它只是要求DbContext查找现有的跟踪实体。如果找到,我们使用它,否则我们创建Box并像第一个示例那样附加它。由于这个附加的引用现在被跟踪,在我们保存更改后,分离“stubbed”框是一个好主意,因为通常这不是一个 complete 框,只是一个存根,如果我们转到EF来获取一个框,我们需要一个完整的框,而不是一个仅具有ID集的跟踪存根。
我之所以说你应该只需要EF去数据库并获取盒子,是因为上面的逻辑很容易搞砸,导致情景运行时bug,当你希望引用一个现有的记录时,它也可以作为一个验证,在一个有效的状态下接收项目,这为您提供了一个有意义的验证点来Assert,而不是
SaveChanges
上的FK异常或更糟的情况,将项目保存在错误/无效的框中。