使用具有 Entity Framework 的文件表以及级联功能
Using File Tables with Entity Framework as well as cascading functionality
SQL 服务器 FileTable
于 2012 年首次推出,但 Entity Framework(.NET Core 或完整的 .NET Framework)不支持它。使用 SQL 服务器 FileTable
或 FileStream
允许更快的文件上传和下载。
我想在我的 .NET Core 应用程序中使用 FileTable
,我必须在其中创建此 FileTable
与另一个简单 table.
的关系
为此,我需要在 C# 中输入 hierarchyid
并在 SQL 服务器实例级别启用文件流支持。 Entity Framework 似乎不支持在更新迁移期间创建数据库时使用 FileTable
支持创建数据库。
早些时候,当我想使用代码优先方法将 SQL File Tables 与我的 .Net Core 应用程序一起使用时,我问了这个问题但没有找到答案。好吧,既然我已经解决了这个问题,我想分享一下。我不会详细说明每一个细节,但是,这在 Microsoft 的网站上有详细定义。
首先,您需要在 SQL 服务器实例级别启用文件流支持。右键单击 SQL 服务器配置管理器中的 MSSQLSERVER 服务(我的 SQL 服务器实例名称是 MSSQLSERVER),然后选择属性。在 FileStream 选项卡中,启用前两个复选框(“Enable Filestream for transact-SQL access & Enable Filestream for file I/O access”)。然后重启服务。
在 SQL Server Management Studio (SSMS) 中,右键单击顶部节点(主要以您的计算机名称命名)并单击属性。转到“高级”选项卡并根据需要更改 FILESTREAM 访问级别(无论是 transact-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 文件 Table 中的文件。 xyz
是后缀 DB.mdf
、Fs
和 DBLog.ldf
以及 N
前缀的数据库名称。我启用了 NON_TRANSACTED_ACCESS
;但如果你不想这样的访问,你可以禁用。请注意,目录必须在您 运行 此查询之前存在。
现在您已经创建了数据库,您可以继续 运行 从连接字符串中具有相同数据库名称的 .Net 应用程序迁移。
您还需要一个 SQL 函数来支持您的操作,我使用 MigrationBuilder
创建了它 class:
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");
AND
migrationBuilder.SQL("CREATE FUNCTION StringToHierarchyId (@Id varchar(max)) "+
"RETURNS hierarchyid WITH SCHEMABINDING AS "+
"BEGIN "+
"RETURN CONVERT(hierarchyid,CONVERT(VARBINARY(MAX),@Id,1),1) "+
"END");
后面我会用到这个功能,并一路讲解它的作用
现在,创建将存储您的文件的文件 Table。当然,您可以为不同类型的文件创建任意数量的文件 table。
migrationBuilder.Sql("CREATE TABLE DocumentStore AS FILETABLE WITH (FileTable_Directory = 'DocumentStore', FileTable_Collate_Filename = database_default);");
我的文件 Table 名称是 DocumentStore
。
如果您想访问 NON-TRANSACTED 方式。下面是代码:
migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");
请注意,FILETABLEROOTPATH('TableName')
是我在存储过程中使用的内置函数。我知道如何在 .Net 中调用存储过程,所以我只是将函数包装在存储过程中。
我创建了另一个存储过程来获取存储在文件 table 中的任何文件的 Path_Locator
。 Path_Locator 是文件 Table 的主键,稍后我需要在另一个 table 中输入该文件作为对该文件的引用。代码是:
migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");
我还使用名为 Documents.cs
的简单 .Net 模型 Class 创建了一个 table,包括普通属性(虽然它们在文件 Table 中也可用,但重复了) 包括文件 Table 中引用文件的属性。由于文件 Table 具有名为 Path_Locator 且类型为 HIERARCHYID
的 PK,我在 Documents
table 中创建了 varchar(max)
字段并将存储 Path_Locator
在从 SQL HIERARCHYID
数据类型转换为 SQL VARCHAR
之后。此 table 是我的域 classes 的一部分,并且将具有 table 通常具有的关系。
现在我已经创建了支持 tables,我还需要实现 CASCADE DELETE 功能,我可以使用 SQL 触发器来实现。第一次触发文件 Table 为:
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"
);
第二个触发器将与 table 一起使用名为 Documents
的迁移来反映文件 Table 中的文件删除(同步数据库):
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");
为了使用这些触发器,我需要将 HIERARCHYID
转换为 VARCHAR(MAX)
,反之亦然,为此我使用 SQL 标量函数来回转换。
现在,为了插入文件,我在 GETPATHLOCATOR
存储过程检索到的位置存储到 windows 文件系统中,文件自动存储到我的文件 Table 中。同时,我还向从 C# 模型 class 创建的其他 table 添加了一条记录,即 Documents
维护两个 tables.
将来,我会尝试创建支持文件流的数据库,使用应用程序中的一些代码在 SQL 服务器实例级别启用文件流支持,以避免在代码过程中出现这种情况。
SQL 服务器 FileTable
于 2012 年首次推出,但 Entity Framework(.NET Core 或完整的 .NET Framework)不支持它。使用 SQL 服务器 FileTable
或 FileStream
允许更快的文件上传和下载。
我想在我的 .NET Core 应用程序中使用 FileTable
,我必须在其中创建此 FileTable
与另一个简单 table.
为此,我需要在 C# 中输入 hierarchyid
并在 SQL 服务器实例级别启用文件流支持。 Entity Framework 似乎不支持在更新迁移期间创建数据库时使用 FileTable
支持创建数据库。
早些时候,当我想使用代码优先方法将 SQL File Tables 与我的 .Net Core 应用程序一起使用时,我问了这个问题但没有找到答案。好吧,既然我已经解决了这个问题,我想分享一下。我不会详细说明每一个细节,但是,这在 Microsoft 的网站上有详细定义。
首先,您需要在 SQL 服务器实例级别启用文件流支持。右键单击 SQL 服务器配置管理器中的 MSSQLSERVER 服务(我的 SQL 服务器实例名称是 MSSQLSERVER),然后选择属性。在 FileStream 选项卡中,启用前两个复选框(“Enable Filestream for transact-SQL access & Enable Filestream for file I/O access”)。然后重启服务。
在 SQL Server Management Studio (SSMS) 中,右键单击顶部节点(主要以您的计算机名称命名)并单击属性。转到“高级”选项卡并根据需要更改 FILESTREAM 访问级别(无论是 transact-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 文件 Table 中的文件。 xyz
是后缀 DB.mdf
、Fs
和 DBLog.ldf
以及 N
前缀的数据库名称。我启用了 NON_TRANSACTED_ACCESS
;但如果你不想这样的访问,你可以禁用。请注意,目录必须在您 运行 此查询之前存在。
现在您已经创建了数据库,您可以继续 运行 从连接字符串中具有相同数据库名称的 .Net 应用程序迁移。
您还需要一个 SQL 函数来支持您的操作,我使用 MigrationBuilder
创建了它 class:
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");
AND
migrationBuilder.SQL("CREATE FUNCTION StringToHierarchyId (@Id varchar(max)) "+
"RETURNS hierarchyid WITH SCHEMABINDING AS "+
"BEGIN "+
"RETURN CONVERT(hierarchyid,CONVERT(VARBINARY(MAX),@Id,1),1) "+
"END");
后面我会用到这个功能,并一路讲解它的作用
现在,创建将存储您的文件的文件 Table。当然,您可以为不同类型的文件创建任意数量的文件 table。
migrationBuilder.Sql("CREATE TABLE DocumentStore AS FILETABLE WITH (FileTable_Directory = 'DocumentStore', FileTable_Collate_Filename = database_default);");
我的文件 Table 名称是 DocumentStore
。
如果您想访问 NON-TRANSACTED 方式。下面是代码:
migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");
请注意,FILETABLEROOTPATH('TableName')
是我在存储过程中使用的内置函数。我知道如何在 .Net 中调用存储过程,所以我只是将函数包装在存储过程中。
我创建了另一个存储过程来获取存储在文件 table 中的任何文件的 Path_Locator
。 Path_Locator 是文件 Table 的主键,稍后我需要在另一个 table 中输入该文件作为对该文件的引用。代码是:
migrationBuilder.Sql("CREATE PROCEDURE GetFileTableRootPath @TableName VARCHAR(100), @Path VARCHAR(1000) OUTPUT AS BEGIN SET @Path = (SELECT FILETABLEROOTPATH(@TableName)) END");
我还使用名为 Documents.cs
的简单 .Net 模型 Class 创建了一个 table,包括普通属性(虽然它们在文件 Table 中也可用,但重复了) 包括文件 Table 中引用文件的属性。由于文件 Table 具有名为 Path_Locator 且类型为 HIERARCHYID
的 PK,我在 Documents
table 中创建了 varchar(max)
字段并将存储 Path_Locator
在从 SQL HIERARCHYID
数据类型转换为 SQL VARCHAR
之后。此 table 是我的域 classes 的一部分,并且将具有 table 通常具有的关系。
现在我已经创建了支持 tables,我还需要实现 CASCADE DELETE 功能,我可以使用 SQL 触发器来实现。第一次触发文件 Table 为:
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"
);
第二个触发器将与 table 一起使用名为 Documents
的迁移来反映文件 Table 中的文件删除(同步数据库):
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");
为了使用这些触发器,我需要将 HIERARCHYID
转换为 VARCHAR(MAX)
,反之亦然,为此我使用 SQL 标量函数来回转换。
现在,为了插入文件,我在 GETPATHLOCATOR
存储过程检索到的位置存储到 windows 文件系统中,文件自动存储到我的文件 Table 中。同时,我还向从 C# 模型 class 创建的其他 table 添加了一条记录,即 Documents
维护两个 tables.
将来,我会尝试创建支持文件流的数据库,使用应用程序中的一些代码在 SQL 服务器实例级别启用文件流支持,以避免在代码过程中出现这种情况。