ESi*_*lis 6 c# sql-server entity-framework entity-framework-core
我很难说清楚这句话,所以我将使用一些代码来帮助解释自己和我的问题。因此,假设我有两个班级ClassA和ClassB:
class ClassA
{
public int ClassAId { get; set; }
public string Name { get; set; }
}
[Owned]
class ClassAOwned
{
public int ClassAId { get; set; }
public string Name { get; set; }
}
class ClassB
{
public int ClassBId { get; set; }
public string Action { get; set; }
public ClassAOwned ClassA { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的ClassBcontains ClassA,但是我有另一个类,ClassAOwned因为我想ClassB拥有ClassA(将其列平展到ClassB表中),而且还拥有ClassADbSet作为单独的表(并且据我所知,实体类不能拥有,并且在拥有自己的DbSet同时),所以我不得不使用2个不同的类。这是我的背景,使其更易于理解:
class TestContext : DbContext
{
public DbSet<ClassA> ClassAs { get; set; }
public DbSet<ClassB> ClassBs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("TestContext");
}
}
Run Code Online (Sandbox Code Playgroud)
现在,当我尝试同时插入ClassA和ClassB上下文并且必须匹配ClassAId数据库提供程序生成的它们的值时,我的问题就来了:
var testContext = new TestContext();
var classA = new ClassA
{
Name = "classAName"
};
var classB = new ClassB
{
Action = "create",
ClassA = new ClassAOwned
{
ClassAId = classA.ClassAId,
Name = classA.Name
}
};
testContext.ClassAs.Add(classA);
testContext.ClassBs.Add(classB);
classB.ClassA.ClassAId = classA.ClassAId;
testContext.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
使用InMemoryDatabase以下调用时:
testContext.ClassAs.Add(classA);
Run Code Online (Sandbox Code Playgroud)
实际上会更改classA.ClassAId为正确的生成值,但是在使用SQL Server时classA.ClassAId将其设置为int.MinValue下一个调用:
classB.ClassA.ClassAId = classA.ClassAId;
Run Code Online (Sandbox Code Playgroud)
设置classB.ClassA.ClassAId为int.MinValue。最后的电话:
testContext.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
更改classA.ClassAId为正确的生成值,但classB.ClassA.ClassAId保持不变int.MinValue,这就是被插入数据库的值。
我的问题是:有没有办法告诉EF核心,将两个实体添加到上下文中时,将一个人的属性设置为为另一个实体的主键生成的任何值?因此,我要寻找的功能与添加两个具有外键的实体完全相同,只是在这种情况下,它并不是真正的外键。
一个简单的解决方法是在“ foreign key”(classB.ClassA.ClassAId)之后设置testContext.SaveChanges()并再次保存更改,但是随后它将变成两个单独的操作,如果第二个操作失败了怎么办?数据库将处于无效状态。
这是可能的,但需要一些技巧,它适用于目前最新的 EF Core 2.2,并且可能在 3.0+ 中停止工作(至少需要验证)。
首先,它必须被映射为关系——没有其他方法。不过,它不需要是真正的数据库关系,从 EF Core 模型的角度来看应该是这样的。
其次,这一点非常重要,删除级联行为应设置为 Restrict,这当前意味着在数据库中强制执行,但对内存中跟踪的相关实体不执行任何操作。
那么让我们对您的示例进行此操作。上述两个映射都需要类似于以下的流畅配置:
modelBuilder.Entity<ClassB>().OwnsOne(e => e.ClassA)
.HasOne<ClassA>().WithMany() // (1)
.OnDelete(DeleteBehavior.Restrict); // (2)
Run Code Online (Sandbox Code Playgroud)
如果您使用迁移,生成的迁移将包含如下内容:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ClassA",
columns: table => new
{
ClassAId = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ClassA", x => x.ClassAId);
});
migrationBuilder.CreateTable(
name: "ClassB",
columns: table => new
{
ClassBId = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Action = table.Column<string>(nullable: true),
ClassA_ClassAId = table.Column<int>(nullable: false),
ClassA_Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ClassB", x => x.ClassBId);
table.ForeignKey(
name: "FK_ClassB_ClassA_ClassA_ClassAId",
column: x => x.ClassA_ClassAId,
principalTable: "ClassA",
principalColumn: "ClassAId",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ClassB_ClassA_ClassAId",
table: "ClassB",
column: "ClassA_ClassAId");
}
Run Code Online (Sandbox Code Playgroud)
手动编辑它并删除ForeignKey命令(行),因为您不需要真正的 FK。您也可以删除相应的CreateIndex命令,尽管这不会有什么坏处。
就这样。您需要记住的唯一重要的事情是仅在新实体已添加到上下文(因此被跟踪)之后才使用主体TableAId属性。IE
var testContext = new TestContext();
var classA = new ClassA
{
Name = "classAName"
};
testContext.ClassAs.Add(classA); // <--
var classB = new ClassB
{
Action = "create",
ClassA = new ClassAOwned
{
ClassAId = classA.ClassAId, // <--
Name = classA.Name
}
};
testContext.ClassBs.Add(classB);
testContext.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
它将生成临时负值,但之后SaveChanged两个 id 将使用实际数据库生成的值进行更新。
| 归档时间: |
|
| 查看次数: |
158 次 |
| 最近记录: |