4

SQL ServerFileTable于 2012 年首次推出,但不受 Entity Framework(.NET Core 或完整的 .NET Framework)支持。使用 SQL ServerFileTableFileStream允许更快的文件上传和下载。

我想FileTable与我的 .NET Core 应用程序一起使用,我必须在其中创建FileTable与另一个简单表的关系。

为此,我需要输入hierarchyidC# 并在 SQL Server 实例级别启用文件流支持。FileTable在更新迁移期间创建数据库时,实体框架似乎不提供对创建数据库的支持。

4

1 回答 1

6

早些时候,当我想使用代码优先方法将 SQL 文件表与我的 .Net Core 应用程序一起使用时,我问了这个问题并没有找到答案。好吧,既然我已经解决了这个问题,我想分享一下。我不会详细介绍每一个细节,但是,这是在 Microsoft 的网站上详细定义的。

首先,您需要在 SQL Server 实例级别启用文件流支持。右键单击 SQL Server 配置管理器中的 MSSQLSERVER 服务(我的 SQL Server 实例名称是 MSSQLSERVER),然后选择属性。在 FileStream 选项卡中,启用前两个复选框(“Enable Filestream for transact-SQL access & Enable Filestream for file I/O access”)。然后重启服务。

在 SQL Server Management Studio (SSMS) 中,右键单击顶部节点(主要以您的计算机名称命名)并单击属性。转到“高级”选项卡并根据您的需要更改 FILESTREAM 访问级别(无论是事务 SQL 还是完全访问)。

此后,您应该通过以下查询创建数据库:

CREATE DATABASE xyz
ON PRIMARY 
(NAME = FS,
    FILENAME = 'D:\Database\xyzDB.mdf'),
FILEGROUP FileStreamFS CONTAINS FILESTREAM(NAME = FStream, 
    FILENAME = 'D:\Database\xyzFs')
LOG ON 
(NAME = FILESDBLog,   
    FILENAME = 'D:\Database\xyzDBLog.ldf')
WITH FILESTREAM (NON_TRANSACTED_ACCESS = FULL, DIRECTORY_NAME = N'xyz')
GO

D:\Database是我存储数据库文件的目录,包括将存储在 SQL 文件表中的文件。xyz是数据库的名称,后缀DB.mdfFs& ,其后DBLog.ldf为前缀。N我启用了NON_TRANSACTED_ACCESS;但如果您不想进行此类访问,则可以禁用。请注意,在运行此查询之前,目录必须存在。

现在您已经创建了数据库,您可以继续从您的 .Net 应用程序运行迁移,并在连接字符串中使用相同的数据库名称。

您还需要一个 SQL 函数来支持您的操作,我使用MigrationBuilder类创建了它:

migrationBuilder.Sql("CREATE FUNCTION dbo.HierarchyIdToString (@Id hierarchyid) RETURNS varchar(max) with schemabinding AS BEGIN RETURN CONVERT(varchar(max),CONVERT(varbinary(max),@Id,1),1); END");

migrationBuilder.SQL("CREATE FUNCTION StringToHierarchyId (@Id varchar(max)) "+
"RETURNS hierarchyid WITH SCHEMABINDING AS "+
"BEGIN "+
"RETURN CONVERT(hierarchyid,CONVERT(VARBINARY(MAX),@Id,1),1) "+
"END");

稍后,我将使用这个功能,并沿途解释它的作用。

现在,创建将存储您的文件的文件表。您当然可以为不同类型的文件创建任意数量的文件表。

migrationBuilder.Sql("CREATE TABLE DocumentStore AS FILETABLE WITH (FileTable_Directory = 'DocumentStore', FileTable_Collate_Filename = database_default);");

我的文件表名称是DocumentStore.

您还需要一个存储过程来获取文件表根路径(它存储文件的位置),以便在文件系统级别访问这些文件,以防您想以非事务方式访问文件。下面是代码:

migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");

请注意,这FILETABLEROOTPATH('TableName')是我在存储过程中使用的内置函数。我知道如何在.Net 中调用存储过程,所以我只是将函数包装在存储过程中。

我创建了另一个存储过程来获取Path_Locator存储在文件表中的任何文件。Path_Locator 是文件表的主键,我稍后需要将其作为对该文件的引用输入到另一个表中。代码是:

migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");

我还使用简单的 .Net 模型类创建了一个表,命名Documents.cs包括正常属性(尽管重复,因为它们在文件表中也可用),包括在文件表中引用文件的属性。由于 File Table 的 PK 名称为 Path_Locator,类型为HIERARCHYID,因此我在表中创建了varchar(max)字段,并将在从 SQL数据类型转换为 SQLDocuments后将其存储在此列中。该表是我的域类的一部分,并且将具有与表通常具有的关系。Path_LocatorVARCHARHIERARCHYID

现在我已经创建了支持表,我还需要实现 CASCADE DELETE 功能,这可以通过使用 SQL 触发器来完成。文件表上的第一个触发器为:

migrationBuilder.Sql(
                "CREATE TRIGGER dbo.CascadeDelete ON DocumentStore "+
                "AFTER DELETE NOT FOR REPLICATION " +
                "AS "+
                "BEGIN "+
                "SET NOCOUNT ON "+
                "DECLARE @Id varchar(max); "+
                "DECLARE @Table Table (MyHierarchy hierarchyid); "+
                "INSERT INTO @Table SELECT deleted.path_locator from deleted; "+
                "WHILE ((SELECT COUNT(*) FROM @Table) > 0) "+
                "BEGIN "+
                "select @Id = dbo.HierarchyIdToString((SELECT TOP 1 * from @Table)); "+
                "DELETE FROM Documents WHERE HierarchyInString = @Id; "+
                "DELETE FROM @Table where MyHierarchy = dbo.StringToHierarchyId(@Id); "+
                "END END"
            );

第二个触发器将使用名为 as 的迁移来处理表,Documents以反映文件表中的文件删除(同步数据库):

migrationBuilder.Sql(
                "CREATE TRIGGER dbo.CascadeDeleteDocuments ON Documents AFTER DELETE NOT FOR REPLICATION AS BEGIN SET NOCOUNT ON DECLARE @Id hierarchyid; DECLARE @Table Table (MyHierarchyInString varchar(max)); INSERT INTO @Table SELECT deleted.HierarchyInString from deleted; WHILE ((SELECT COUNT(*) FROM @Table) > 0) BEGIN select @Id = dbo.StringToHierarchyId((SELECT TOP 1 * from @Table)); DELETE FROM DocumentStore WHERE path_locator = @Id; DELETE FROM @Table where MyHierarchyInString = dbo.HierarchyIdToString(@Id); END END");

为了让这些触发器工作,我需要转换HIERARCHYIDwith VARCHAR(MAX),反之亦然,为此我使用 SQL Scalar 函数来回转换。

现在,要插入文件,我将存储到从GETPATHLOCATOR存储过程中检索到的位置的 windows 文件系统中,并且文件会自动存储到我的文件表中。同时,我还将一条记录添加到从 C# 模型类创建的另一个表中,即Documents维护两个表。

将来,我将尝试创建具有文件流支持的数据库,使用应用程序中的一些代码在 SQL Server 实例级别启用文件流支持,以避免在代码过程中出现这种情况。

于 2020-03-27T18:42:12.573 回答