使用 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_ChargeCarrierPickedUpOnTransportedByDevice。当叉车卸载运载工具时,应使用卸载日期和货物运输到的存储位置的 ID 更新条目。

ID_ChargeCarrier 必须始终填写。对于这些 ID 中的每一个,只能有一个条目将 ID_Storage 设置为 NULL,如 IX_OnForklift 所定义。电荷载体可以多次出现在同一个存储器上。

我可以将 ID_ChargeCarrierID_StoragePickedUpOn 的组合作为主键,但这也不起作用,因为 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",所以现在装卸完全抽象了。