你可以创建一个CLR UDT来实现跨数据库共享表格类型吗?

3
如果我有这样的一个 SQL 语句:
CREATE TYPE [dbo].[typeRateLimitVariables] AS TABLE(
            [vchColumnName] [varchar](250) NULL,
            [decColumnValue] [decimal](25, 10) NULL
)

如果我将其用作数据库中UDF的表变量,则具有足够的范围。但是,假设我想从同一服务器上的另一个数据库调用标量UDF,那么就会出现未知类型错误。

我已经尝试在调用DB上创建类型,但显然我会得到类型不匹配的错误,因为虽然每个UDT都具有相同的名称,但它们具有不同的作用域,因此是不同的类型。

我知道您可以创建CLR类型,将程序集注册到SQL Server,然后普遍访问自定义类型。

我的想法是创建类型为“ TABLE”的CLR UDT,但我看不出如何实现这一点,因为我知道它必须是CLR类型“ SqlDbType.Structured”;

我的问题是:

  1. 是否有方法在不使用CLR的情况下创建SQL 2008 R2中的全局范围以用于表变量?如果没有……
  2. 如何在C#CLR中定义UDT,其中UDT本质上是UDT“AS TABLE”

似乎与大多数UDT讨论的主题不符,但对于问题#1 - 必须是表变量吗?变量(包括表变量)仅限于调用上下文 - 即命令。您可以使用完整符号([数据库]。[角色]。[对象])编写多数据库操作,可以在一个查询中将数据插入和从表变量中取出,只要它们存在。#temp表也可能有效。 - jklemmack
是的,在这种情况下,我想要一个可以携带类型的UDT,所以它需要是一个UDT;解决方案非常庞大 - 所以是的;#temp通常是一个很好的解决方案,但在这个特定的解决方案中,我们在整个展示中都使用了UDT,而不仅仅是在一个临时查询中。 - Andrew La Grange
1个回答

4
我知道你可以创建CLR类型,将程序集注册到SQL Server,然后普遍访问自定义类型。
你确定吗?用户定义类型是数据库级别对象,而不是服务器级别。唯一的“普遍”访问方式是在每个数据库中加载程序集并创建用户定义类型。MSDN文档Registering User-Defined Types in SQL Server明确说明了这一点。
使用跨数据库的UDT
UDT的定义范围仅限于单个数据库。因此,在一个数据库中定义的UDT不能在另一个数据库中的列定义中使用。为了在多个数据库中使用UDT,必须在每个数据库中执行CREATE ASSEMBLY和CREATE TYPE语句,并使用相同的程序集。如果程序集具有相同的名称、强名称、区域设置、版本、权限集和二进制内容,则认为它们是相同的。
一旦UDT在两个数据库中都注册并可访问,就可以将一个数据库中的UDT值转换为另一个数据库中使用。相同的UDT可以在以下情况下跨数据库使用:
- 调用在不同数据库中定义的存储过程。 - 查询在不同数据库中定义的表。 - 从一个数据库表的UDT列中选择UDT数据,并将其插入到具有相同UDT列的第二个数据库中。
在这些情况下,服务器需要的任何转换都会自动发生。您无法使用Transact-SQL CAST或CONVERT函数显式执行转换。

1)如果没有使用CLR,有没有办法在SQL 2008 R2中为表变量创建全局作用域?如果没有...

除了MSDN文档中提到的CLR UDT之外,表类型和用户定义类型都无法跨数据库访问。

2)如何在C# CLR中定义UDT,其中UDT本质上是“AS TABLE”的UDT?

您不能这样做,因为它们是两个不同的东西(即“类型”与“表类型”),而不仅仅是两种不同的实现方式(即T-SQL UDF / Stored Proc与SQLCLR UDF / Stored Proc)。

编辑:

从纯技术角度来看,可以使用类型(表类型和用户定义类型)跨数据库使用,但只能通过使用USE命令切换当前上下文,在动态SQL中使用。因此,这种用法在实际应用层面上具有有限的适用性,但仍然是可能的,如以下示例所示:

SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
GO

USE [msdb];
GO

PRINT 'Creating [GlobalTableDef] Table Type in [msdb]...';
CREATE TYPE dbo.GlobalTableDef
AS TABLE
(
    [ID] INT NOT NULL IDENTITY(17, 22),
    [CreateDate] DATETIME NOT NULL DEFAULT (GETDATE()),
    [Something] NVARCHAR(2000) NULL
);
GO

PRINT 'Creating [TotalBytes] Function in [msdb]...';
GO
CREATE FUNCTION dbo.TotalBytes
(
    @TableToSummarize dbo.GlobalTableDef READONLY
)
RETURNS INT
AS
BEGIN
    DECLARE @TotalBytes INT = 0;

SELECT  @TotalBytes += (4 + 8 + DATALENGTH(COALESCE(tmp.Something, '')))
    FROM    @TableToSummarize tmp;

    RETURN @TotalBytes;
END;
GO

PRINT 'Testing the Table Type and Function...';
DECLARE @TmpTable dbo.GlobalTableDef;
INSERT INTO @TmpTable (Something) VALUES (N'this is a test');
INSERT INTO @TmpTable (Something) VALUES (NULL);
INSERT INTO @TmpTable (Something) VALUES (N'still seems to be a test');

SELECT * FROM @TmpTable;

SELECT dbo.TotalBytes(@TmpTable) AS [TotalBytesUsed];
GO

USE [tempdb];
GO
PRINT 'Creating [TypeTest] Proc in [tempdb]...';
GO

CREATE PROCEDURE dbo.TypeTest
AS
SET NOCOUNT ON;

    SELECT 1 AS [Step], DB_NAME() AS [CurrentDB];

    EXEC('
        SELECT 2 AS [Step], DB_NAME() AS [CurrentDB];
        USE [msdb];
        SELECT 3 AS [Step], DB_NAME() AS [CurrentDB];
        DECLARE @TmpTable dbo.GlobalTableDef;
        USE [tempdb];
        SELECT 4 AS [Step], DB_NAME() AS [CurrentDB];

        -- local query to prove context is tempdb
        SELECT TOP 5 * FROM sys.objects;

        INSERT INTO @TmpTable (Something) VALUES (N''this is a new test'');
        INSERT INTO @TmpTable (Something) VALUES (NULL);
        INSERT INTO @TmpTable (Something) VALUES (N''non-empty value'');
        INSERT INTO @TmpTable (Something) VALUES (NULL);
        INSERT INTO @TmpTable (Something) VALUES (N''woo-hoo!!!!!!!!!!!!!!!'');
        SELECT * FROM @TmpTable;

        SELECT [msdb].dbo.TotalBytes(@TmpTable) AS [TotalBytesUsed];
    ');

GO

USE [master];
GO

SELECT 5 AS [Step], DB_NAME() AS [CurrentDB];
EXEC tempdb.dbo.TypeTest;

--------------------------------

USE [tempdb];
GO
IF (OBJECT_ID(N'tempdb.dbo.TypeTest') IS NOT NULL)
BEGIN
    PRINT 'Dropping [TypeTest] Proc from [tempdb]...';
    DROP PROCEDURE dbo.TypeTest;
END;
GO

USE [msdb];
GO
IF (OBJECT_ID(N'dbo.TotalBytes') IS NOT NULL)
BEGIN
    PRINT 'Dropping [TotalBytes] Function from [msdb]...';
    DROP FUNCTION dbo.TotalBytes;
END;
GO

IF (EXISTS(
        SELECT  *
        FROM    sys.table_types stt
        WHERE   stt.name = N'GlobalTableDef'
    ))
BEGIN
    PRINT 'Dropping [GlobalTableDef] Table Type from [msdb]...';
    DROP TYPE dbo.GlobalTableDef;
END;
GO

在语句“I know you can create CLR types, register the assembly to SQL Server, and then access the custom type universally.”中,我的意思是CLR程序集可以被捆绑到单个表的作用域中。 - Andrew La Grange
关于这个问题的最后一点是,我们最终采用的解决方案,并且我越来越依赖的是使用模式来进行分离,而不是整个数据库,除非绝对必要,在对相关但分离的数据结构进行分类时。 - Andrew La Grange
关于“普遍访问定制类型”的声明,我曾误解“普遍”是跨数据库的意思。就通过模式和数据库进行分离而言,两者都有优缺点,像往常一样。如果您有大量数据,则独立的数据库可以允许单独备份等操作。但是使用单独的Schema的同一数据库可以允许外键,这很不错,并且通常可以访问类型,正如您所知道的那样。因此,很高兴你有了可行的方案:-)。 - Solomon Rutzky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接