【问题标题】:A flexible foreign key灵活的外键
【发布时间】:2015-08-12 21:12:46
【问题描述】:

我有一个 sql server 数据库。我正在开发一个 vb.net 应用程序。

现在我正在考虑创建一个“事件”表来保存与我的数据库相关的所有事件。但是这个表的其中一个字段应该是一个字段ObjectID,它与这个事件必须做的记录相关。但是这个记录可能在不同的表上。例如:

  • 事件 1 ---- 在表 Clients 上记录 25
  • 事件 2 ---- 记录 30 张表发票
  • 事件 3 ---- 记录 40 条表格文章 ...

问题是ObjectID这个字段应该是一个灵活的外键,因为可能与不同的表相关。

有什么办法可以解决这个问题吗?

谢谢!

【问题讨论】:

  • 你能举一个Client EventInvoice Event的例子吗?您可能只需要Event 上的限定符列来指示每个事件的类型。很难确切地说出你在问什么
  • 您可以将其编入索引以加快连接速度。但是您必须有一些其他列来定义 objectId 的其他表。或者,您必须为要为其设置事件的每个表都有一个可为空的列,并在每列上设置外键。
  • @Plitonix 我的问题是如何读取为其创建此事件的特定表上的特定记录?我正在使用实体框架..

标签: sql-server vb.net entity-framework entity-framework-6


【解决方案1】:

解决此问题的一种方法是将一个表添加到您的数据库中,以充当其他表的基础,并通过与其他表的一对一关系将其连接起来,然后将事件表连接到此基础表.
这将允许您保持每个表的数据完整性。
基表可以简单到只有一列,也可以具有所有其他表共有的列,从而在您的数据结构中实现一种“继承”。

创建基表(假设其他表之间没有公共列):

CREATE TABLE TblObjectBase 
(
    ObjectBase_Id int IDENTITY(1,1) PRIMARY KEY
)

然后,对于需要由ObjectIdEvents 表中引用的任何其他表:

CREATE TABLE TblClients 
(
    Client_Id int PRIMARY KEY,
    Client_FirstName varchar(10),
    Client_LastName varchar(10),
    --  Other client related data
    CONSTRAINT FK_TblClients_TblObjectBase
               FOREIGN KEY(Client_Id) 
               REFERENCES TblObjectBase(ObjectBase_Id)
)

CREATE TABLE TblInvoices
(
    Invoice_Id int PRIMARY KEY,
    -- other incoice related data
     CONSTRAINT FK_TblInvoices_TblObjectBase
               FOREIGN KEY(Invoice_Id) 
               REFERENCES TblObjectBase(ObjectBase_Id)
)

剩下的唯一事情就是在 TblObjectBase 中插入一个新值,以便在其他表上进行任何插入。这可以通过存储过程或插入触发器轻松实现。
插入过程可能如下所示:

CREATE PROCEDURE Insert_TblClients
(
    @Client_FirstName varchar(10),
    @Client_LastName varchar(10),
    -- any other client related data you might have
)
AS
DECLARE @ClientId int

-- Insert a new record to the base table:
INSERT INTO TblObjectBase DEFAULT VALUES;

-- Get the id you've just inserted:
SELECT @ClientId = SCOPE_IDENTITY();

-- Insert the data to the clients table:
INSERT INTO TblClients 
(Client_Id, Client_FirstName, Client_LastName.....) VALUES
(@ClientId, @Client_FirstName, @Client_LastName...)

而不是插入触发器如下所示:

CREATE TRIGGER TblClients_IO_Insert ON TblClients INSTEAD OF INSERT 
AS
BEGIN

DECLARE @ClientId int

-- Insert a new record to the base table:
INSERT INTO TblObjectBase DEFAULT VALUES;

-- Get the id you've just inserted:
SELECT @ClientId = SCOPE_IDENTITY();

INSERT INTO TblClients 
(Client_Id, Client_FirstName, Client_LastName.....) 
SELECT @ClientId, Client_FirstName, Client_LastName..... 
FROM inserted

END

如果您选择使用而不是插入,Identity 值来自另一个表的事实应该对客户端(您的 vb.net 程序)是透明的。

【讨论】:

  • 你认为这个想法适合我的情况吗,因为我在我的应用程序上使用实体框架?
  • 我从未使用过实体框架,所以我不能说。我只是建议了一种保持 fk 关系的方法。你为什么不尝试几个表,看看是否 EF能应付吗?
  • 好的,但是你能向我解释一下,这个基表将如何连接到其他表,所以我需要一个字段作为每个表的 FK 或???
  • 我在您的模型中没有看到的是如何读取事件。我的意思是如果我想获取与事件相关的特定表中的特定记录?
  • 嗯,在这个模型中,所有以 1:1 关系连接的表基本上共享一个标识列。这意味着 client_id 值不能与 Invoices_id 列相同。您可以在 TblObjectBase 表中添加一个列,该列将说明记录所引用的表的名称(如果它会使您的代码更容易),或者可以在事件表中添加一个列,该列将说明引发事件的对象类型。但是,关系完整性不需要它,因为所有可以引发事件的表的 id 列在共享 1:1 的所有表中都是独占的
【解决方案2】:

SQL Server 不支持创建此类约束。

但是您可以通过编程方式模拟链接而没有太多麻烦。

CREATE TABLE tbl_Event
(
    [idEvent]       INT IDENTITY(1,1) NOT NULL,
    [TableSource]   INT NOT NULL,
    [SourceId]      INT NOT NULL
    --Events fields
    CONSTRAINT [PK_Tests] PRIMARY KEY CLUSTERED 
    (
        [id] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

在上面的例子中,SourceId 是外键
TableSource 用于知道外键来自哪个表。
在表源中,您可以使用表的 sys.objects.object_id。
但是,由于您对那些由 SQL 管理的键没有太多控制权,因此我建议您使用自己的表,并为每个表定义常量而不是 sys.objects。
通过这种方式,您还可以更好地控制该表中哪个表可以有外键,并且它变得非常方便加班。

CREATE TABLE tbl_tableSource(
    [idTableSource] INT IDENTITY(1,1) NOT NULL,
    [Constant]      INT NOT NULL,
    [Name]          NVARCHAR(255) NULL
 CONSTRAINT [PK_Tests] PRIMARY KEY CLUSTERED 
(
    [idTableSource] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

INSERT INTO tbl_tableSource
VALUES(1000, 'tbl_SomeTable')

INSERT INTO tbl_tableSource
VALUES(2000, 'tbl_SomeOtherTable')

此外,这种关系在性能方面不如标准关系好。因此,您的键和常量的字段类型非常重要。它不应该很重。这是因为您需要在 tbl_event 上创建一个索引。

要模拟级联删除ON,你需要像这样在父表上实现触发器:

CREATE TRIGGER OneParentTableOnDelete ON  tbl_SomeTable
FOR DELETE
AS 
BEGIN

    DELETE tbl_Event
    FROM DELETED
    INNER JOIN tbl_Event ON tbl_Event.TableSource = [Constant for tbl_SomeTable]
                         AND tbl_Event.idSource = DELETED.id --PrimaryKey


END

要检索数据,您可以这样做

--For events about one foreign key
SELECT *
FROM tbl_event
WHERE tbl_Event.TableSource = [Constant for tbl_SomeTable]
AND tbl_Event.idSource = @idPrimareyKeyOfSomeTable

--Fore events about multiple foreign key
SELECT *
FROM [tbl_SomeTable]
INNER JOIN tbl_event ON tbl_Event.TableSource = [Constant for tbl_SomeTable]
                      AND tbl_Event.idSource = [tbl_SomeTable].id --PrimaryKey

【讨论】:

  • 在我的 VB.net 程序中,我使用实体框架。是否可以构造一个 EF 查询来获取具有您的结构的数据?
  • 我在实体框架方面的经验非常少。可能不适用于实体框架的部分是触发器。 stackoverflow.com/questions/21943314/…
【解决方案3】:

您可以创建一个“索引视图”——定义它但是您喜欢从客户表的记录 25、发票表的记录记录 30 中提取 objectIDs +任何其他相关字段,如您所描述的,等等。

你可以用存储过程做同样的事情。

取决于您打算如何使用数据 - 如果您只是将其拉入 vb.net 数据表,则这些选项中的任何一个都应该可以正常工作。

这种方法的一个好处是您不必在数据库中创建或维护任何新表。

【讨论】:

  • 更多关于存储过程的信息:msdn.microsoft.com/en-us/library/ms187926.aspx
  • 你认为这个解决方案可以在我使用 Entity Framework 6 的应用程序中工作吗?
  • 您可以将实体映射到存储过程(这里有一些示例:msdn.microsoft.com/en-us/data/gg699321.aspx)。您也可以将实体映射到视图。您不必在数据库中构建查询(通常我更喜欢这样做),但您也可以通过直接从应用程序查询来构建您的实体。很多选择。
猜你喜欢
  • 1970-01-01
  • 2010-10-25
  • 2011-10-08
  • 1970-01-01
  • 1970-01-01
  • 2020-03-20
  • 1970-01-01
  • 2021-12-23
  • 2017-08-07
相关资源
最近更新 更多