使用 Entity Framework 在没有主键的情况下写入 table
Write to table without primary key using Entity Framework
我有一个 table,我想插入带有 entity framework 的条目。按照设计,table 不能有有意义的主键。
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
GO
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
GO
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
GO
table 包含电荷载体和存储位置的轨道列表。三个非 ID 字段包含有关哪台叉车移动电荷载体以及何时移动的信息。系统创建的每个承运人的初始条目仅包含卸载日期和两个 ID。当叉车拿起东西时,应该创建一个新条目,只设置三个字段 ID_ChargeCarrier
、PickedUpOn
和 TransportedByDevice
。当叉车卸载运载工具时,应使用卸载日期和货物运输到的存储位置的 ID 更新条目。
ID_ChargeCarrier
必须始终填写。对于这些 ID 中的每一个,只能有一个条目将 ID_Storage
设置为 NULL
,如 IX_OnForklift
所定义。电荷载体可以多次出现在同一个存储器上。
我可以将 ID_ChargeCarrier
、ID_Storage
和 PickedUpOn
的组合作为主键,但这也不起作用,因为 MS-SQL 没有不允许 PK 具有可为空的列。
如您所见,没有其他有意义的主键。我绝对不想为了让 EF 高兴而引入一个多余的 ID 列。
如何通过 Entity Framework 使插入正常工作?
从评论中我看到这是在处理遗留系统。
什么代码会被认为是这个数据的"owner",还有多少其他地方(代码、系统、报告等)"touch"这个数据?您是否预见到需要通过 EF 查询此数据?
如果您只需要根据事件插入行并且不关心通过 EF 查询此数据(至少在可预见的将来),那么我建议仅通过原始插入 SQL 语句并完成它。其他领域的新代码可能开始利用 EF,但 "being consistent for consistency's sake" 从来不是我提出的论点。 :)
table 设计很差。如果这个重构需要依赖这个 table,并且其他接触点是可管理的,那么我会争论将 table 重新设计成这样的东西:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
EventTypeId INT,
EventOn DATETIME2(2) NOT NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
其中 EventTypeId 反映了 Pickup 或 DropOff,EventOn 是日期。这将在 ID_ChargeCarrier、EventTypeId 和 EventOn 之间容纳 PK/unique 约束。
见鬼,扔一个 PK 列,然后将 TransportedByDevice 重构为 FK 以保存 space,因为我猜这个 table 将容纳大量记录。将现有数据移植到新结构中除了处理时间外应该不会造成任何问题。
或者至少保持相同的兼容结构,将适当的 PK 附加到 table。例如,您可以使用:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
ID_ChargeCarrierStorage INT IDENTITY(1,1) NOT NULL
/w 对新标识列的 PK 约束。这应该能够在没有 table 重新创建的情况下附加。但是,我预计这个 table 可能会非常大,因此这将反映出一个相当昂贵的操作,应该进行相应的测试和安排。
EF 需要定义一个键来确定唯一的行标识符。它甚至不需要在数据库中声明为 PK,尽管它仍然限于使用不可为 null 的字段。如果这些是您将在系统的整个生命周期中访问的记录,我强烈建议使用可容纳合法 PK 的数据库结构。我有 tables 绑定到没有定义 PKs 的实体,但这些是严格的瞬态暂存 tables,我从 Excel 等外部源加载数据,连接一些实体到 table 来处理数据并将相关位移动到永久 table.
EF 只需要一个实体密钥。它不必映射到真实的数据库主键。
并且您应该 在数据库中的映射字段上放置一个唯一索引(否则可能会出现性能不佳和行为异常的风险)。
在 SQL 服务器唯一索引可以有可为空的列。您可以将不可为 null 的实体属性映射到可为 null 的数据库列。
这是一个使用 table 定义的示例:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace Ef6Test
{
class ChargeCarrier_Storage
{
[Key()]
[Column(Order =0)]
public int ID_ChargeCarrier { get; set; }
[Key()]
[Column(Order = 1)]
public int ID_Storage { get; set; }
[Key()]
[Column(Order = 2)]
public DateTime PickedUpOn { get; set; }
public DateTime UnloadedOn { get; set; }
public string TransportedByDevice { get; set; }
}
class Db : DbContext
{
public Db(string constr) : base(constr) { }
public DbSet<ChargeCarrier_Storage> ChargeCarrier_Storage { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
class MyDbInitializer : IDatabaseInitializer<Db>
{
public void InitializeDatabase(Db context)
{
var sql = @"
drop table if exists dbo.ChargeCarrier_Storage;
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
--CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
--CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
";
context.Database.ExecuteSqlCommand(sql);
}
}
class Program
{
static string constr = "server=.;database=ef6test;integrated security=true";
static void Main(string[] args)
{
Database.SetInitializer<Db>( new MyDbInitializer());
using (var db = new Db(constr))
{
var f = new ChargeCarrier_Storage();
f.ID_ChargeCarrier = 2;
f.ID_Storage = 2;
f.PickedUpOn = DateTime.Now;
f.TransportedByDevice = "SomeDevice";
db.ChargeCarrier_Storage.Add(f);
db.SaveChanges();
}
using (var db = new Db(constr))
{
var c = db.ChargeCarrier_Storage.First();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
最终我 "solved" 通过完全重做 table 解决了这个问题。我删除了很多混乱并添加了一个主键。
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NOT NULL,
ID_User INT NULL,
StoredOn DATETIME2(2) NOT NULL DEFAULT GetDate(),
CONSTRAINT PK_ChargeCarrier_Storage PRIMARY KEY (ID_ChargeCarrier, ID_Storage, StoredOn),
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID),
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID)
)
GO
我认为叉车是它自己的"storage",所以现在装卸完全抽象了。
我有一个 table,我想插入带有 entity framework 的条目。按照设计,table 不能有有意义的主键。
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
GO
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
GO
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
GO
table 包含电荷载体和存储位置的轨道列表。三个非 ID 字段包含有关哪台叉车移动电荷载体以及何时移动的信息。系统创建的每个承运人的初始条目仅包含卸载日期和两个 ID。当叉车拿起东西时,应该创建一个新条目,只设置三个字段 ID_ChargeCarrier
、PickedUpOn
和 TransportedByDevice
。当叉车卸载运载工具时,应使用卸载日期和货物运输到的存储位置的 ID 更新条目。
ID_ChargeCarrier
必须始终填写。对于这些 ID 中的每一个,只能有一个条目将 ID_Storage
设置为 NULL
,如 IX_OnForklift
所定义。电荷载体可以多次出现在同一个存储器上。
我可以将 ID_ChargeCarrier
、ID_Storage
和 PickedUpOn
的组合作为主键,但这也不起作用,因为 MS-SQL 没有不允许 PK 具有可为空的列。
如您所见,没有其他有意义的主键。我绝对不想为了让 EF 高兴而引入一个多余的 ID 列。
如何通过 Entity Framework 使插入正常工作?
从评论中我看到这是在处理遗留系统。
什么代码会被认为是这个数据的"owner",还有多少其他地方(代码、系统、报告等)"touch"这个数据?您是否预见到需要通过 EF 查询此数据?
如果您只需要根据事件插入行并且不关心通过 EF 查询此数据(至少在可预见的将来),那么我建议仅通过原始插入 SQL 语句并完成它。其他领域的新代码可能开始利用 EF,但 "being consistent for consistency's sake" 从来不是我提出的论点。 :)
table 设计很差。如果这个重构需要依赖这个 table,并且其他接触点是可管理的,那么我会争论将 table 重新设计成这样的东西:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
EventTypeId INT,
EventOn DATETIME2(2) NOT NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
其中 EventTypeId 反映了 Pickup 或 DropOff,EventOn 是日期。这将在 ID_ChargeCarrier、EventTypeId 和 EventOn 之间容纳 PK/unique 约束。 见鬼,扔一个 PK 列,然后将 TransportedByDevice 重构为 FK 以保存 space,因为我猜这个 table 将容纳大量记录。将现有数据移植到新结构中除了处理时间外应该不会造成任何问题。
或者至少保持相同的兼容结构,将适当的 PK 附加到 table。例如,您可以使用:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
ID_ChargeCarrierStorage INT IDENTITY(1,1) NOT NULL
/w 对新标识列的 PK 约束。这应该能够在没有 table 重新创建的情况下附加。但是,我预计这个 table 可能会非常大,因此这将反映出一个相当昂贵的操作,应该进行相应的测试和安排。
EF 需要定义一个键来确定唯一的行标识符。它甚至不需要在数据库中声明为 PK,尽管它仍然限于使用不可为 null 的字段。如果这些是您将在系统的整个生命周期中访问的记录,我强烈建议使用可容纳合法 PK 的数据库结构。我有 tables 绑定到没有定义 PKs 的实体,但这些是严格的瞬态暂存 tables,我从 Excel 等外部源加载数据,连接一些实体到 table 来处理数据并将相关位移动到永久 table.
EF 只需要一个实体密钥。它不必映射到真实的数据库主键。
并且您应该 在数据库中的映射字段上放置一个唯一索引(否则可能会出现性能不佳和行为异常的风险)。
在 SQL 服务器唯一索引可以有可为空的列。您可以将不可为 null 的实体属性映射到可为 null 的数据库列。
这是一个使用 table 定义的示例:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace Ef6Test
{
class ChargeCarrier_Storage
{
[Key()]
[Column(Order =0)]
public int ID_ChargeCarrier { get; set; }
[Key()]
[Column(Order = 1)]
public int ID_Storage { get; set; }
[Key()]
[Column(Order = 2)]
public DateTime PickedUpOn { get; set; }
public DateTime UnloadedOn { get; set; }
public string TransportedByDevice { get; set; }
}
class Db : DbContext
{
public Db(string constr) : base(constr) { }
public DbSet<ChargeCarrier_Storage> ChargeCarrier_Storage { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
class MyDbInitializer : IDatabaseInitializer<Db>
{
public void InitializeDatabase(Db context)
{
var sql = @"
drop table if exists dbo.ChargeCarrier_Storage;
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
--CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
--CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
";
context.Database.ExecuteSqlCommand(sql);
}
}
class Program
{
static string constr = "server=.;database=ef6test;integrated security=true";
static void Main(string[] args)
{
Database.SetInitializer<Db>( new MyDbInitializer());
using (var db = new Db(constr))
{
var f = new ChargeCarrier_Storage();
f.ID_ChargeCarrier = 2;
f.ID_Storage = 2;
f.PickedUpOn = DateTime.Now;
f.TransportedByDevice = "SomeDevice";
db.ChargeCarrier_Storage.Add(f);
db.SaveChanges();
}
using (var db = new Db(constr))
{
var c = db.ChargeCarrier_Storage.First();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
最终我 "solved" 通过完全重做 table 解决了这个问题。我删除了很多混乱并添加了一个主键。
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NOT NULL,
ID_User INT NULL,
StoredOn DATETIME2(2) NOT NULL DEFAULT GetDate(),
CONSTRAINT PK_ChargeCarrier_Storage PRIMARY KEY (ID_ChargeCarrier, ID_Storage, StoredOn),
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID),
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID)
)
GO
我认为叉车是它自己的"storage",所以现在装卸完全抽象了。