数据访问方法比较 - 第二部分
这是关于 .NET 环境中数据访问方法比较的第二部分。
介绍
这是关于 .NET 环境中数据访问方法比较的第二部分。在这一部分,我将重点关注插入操作。我将比较 ADO.NET 和 NHibernate。总的来说,NHibernate 在向数据库插入数据时可能会比较慢,尤其是在处理大量数据集时。为了提高性能,NHibernate 引入了 Hilo。我将在本文中向您展示如何使用 Hilo,并说明它是什么以及它可能给我们带来的挑战。尽管比较是在 .NET 环境下的 SQL Server 数据库上进行的,但其概念和结果应该适用于 Java 和 Oracle 等其他环境。
背景
在 本文的第一部分,我演示了如何在 .NET 应用程序中设置 NHibernate,并对不同数据访问方法的读取操作进行了比较。在这一部分,我强烈建议您查看 第一部分,如果您还不是 NHibernate 专家的话。这一部分设置 NHibernate 环境的代码与第一部分几乎相同,我将不再赘述细节。我将快速进入使用 NHibernate 进行插入操作,并与不使用 NHibernate 的直接 ADO.NET 方法进行性能比较。由于我们只处理插入操作,因此我在示例应用程序中没有包含 Dapper。
示例数据库
本文中的示例使用了 Visual Studio 2010 和 SQL Server 2008 R2。如果您想自己重复这些示例,您将需要 SQL Server,并且需要管理员权限。如果您在数据库服务器上运行以下脚本,您将获得示例数据库以及示例所需的所有表和存储过程。
SET NOCOUNT ON
USE [master]
GO
-- Create a login user
IF EXISTS
(SELECT * FROM sys.server_principals
WHERE name = N'TestUser')
BEGIN
DROP LOGIN [TestUser]
END
GO
EXEC sp_addlogin @loginame = 'TestUser',
@passwd = 'Password123';
GO
-- Create the database
IF EXISTS
(SELECT name FROM sys.databases
WHERE name = N'SQLPerformanceInsert')
BEGIN
ALTER DATABASE [SQLPerformanceInsert] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
DROP DATABASE [SQLPerformanceInsert]
END
GO
CREATE DATABASE SQLPerformanceInsert
GO
USE [SQLPerformanceInsert]
GO
-- Grant the required database access to the login
sp_grantdbaccess 'TestUser'
GO
sp_addrolemember 'db_datareader', 'TestUser'
GO
sp_addrolemember 'db_datawriter', 'TestUser'
GO
CREATE TABLE [dbo].[THiloIDGenerator](
[TableName] [varchar](50) NOT NULL,
[Hi] [int] NOT NULL,
CONSTRAINT [PK_THilo] PRIMARY KEY CLUSTERED
(
[TableName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TStoredProcedureIDGenerator](
[TableName] [varchar](50) NOT NULL,
[NextId] [int] NOT NULL,
CONSTRAINT [PK_TIDGenerator] PRIMARY KEY CLUSTERED
(
[TableName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TStudent_Identity](
[Id] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](50) NULL,
[FirstName] [varchar](50) NULL,
[Score] [int] NULL,
CONSTRAINT [PK_TStudent_Identity] 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]
GO
CREATE TABLE [dbo].[TStudent_StoredProcedure](
[Id] [int] NOT NULL,
[LastName] [varchar](50) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[Score] [int] NOT NULL,
CONSTRAINT [PK_TStudent_StoredProcedure] 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]
GO
CREATE TABLE [dbo].[TStudent_Hilo](
[Id] [int] NOT NULL,
[LastName] [varchar](50) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[Score] [int] NOT NULL,
CONSTRAINT [PK_TStudent_Hilo] 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]
GO
-- Initiate the Hilo table and the ID Generator table
INSERT INTO [THiloIDGenerator] VALUES ('TStudent_Hilo', 0)
INSERT INTO [TStoredProcedureIDGenerator]
VALUES ('TStudent_StoredProcedure', 1)
GO
CREATE TYPE [dbo].[StudentTableType] AS TABLE(
[Id] [int] NOT NULL,
[LastName] [varchar](50) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[Score] [int] NOT NULL
)
GO
CREATE PROCEDURE [dbo].[InsertStudentsByStoredProcedure]
@Students AS StudentTableType READONLY
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE @FirstID AS INT
DECLARE @Count AS INT
SELECT @Count = COUNT(*) FROM @Students
BEGIN TRANSACTION
BEGIN TRY
SELECT @FirstID = NextId
FROM TStoredProcedureIDGenerator
WHERE TableName = 'TStudent_StoredProcedure'
UPDATE TStoredProcedureIDGenerator
SET NextId = @FirstID + @Count
WHERE TableName = 'TStudent_StoredProcedure'
INSERT INTO TStudent_StoredProcedure
SELECT Id + @FirstID, LastName, FirstName, Score
FROM @Students
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION
RAISERROR ('Unable to save students', 16, 1)
RETURN
END CATCH
IF @@TRANCOUNT > 0
COMMIT TRANSACTION
SELECT @FirstID AS FirstId
END
GO
GRANT EXECUTE ON TYPE::[dbo].[StudentTableType] TO TestUser
GRANT EXECUTE ON OBJECT::[dbo].[InsertStudentsByStoredProcedure]
TO TestUser
GO
-- Bring the database on-line
ALTER DATABASE [SQLPerformanceInsert] SET MULTI_USER
GO
PRINT 'SQLPerformanceInsert database is created'
成功运行脚本后,数据库服务器上将有一个名为 [SQLPerformanceInsert] 的数据库。
在 [SQLPerformanceInsert] 中,我们有五个表和一个存储过程。以下三个表结构几乎完全相同,只是 [ID] 字段在 [TStudent_Identity] 表中是一个标识字段。
- TStudent_Identity
- TStudent_Hilo
- TStudent_StoredProcedure
在此示例中,我们将通过不同的数据访问方法上传一个包含 5000 名人工生成学生的 XML 文件,并比较每种方法所花费的时间。当我们将学生插入数据库时,我们将为每个学生分配一个 ID。[THiloIDGenerator] 表是为插入到 [TStudent_Hilo] 表中的学生生成 ID 的表。其定义如下:
创建数据库时,其初始化方式如下:
存储过程 [InsertStudentsByStoredProcedure] 将在示例中使用,通过直接调用 ADO.NET 存储过程将学生插入到 [TStudent_StoredProcedure] 表中。它使用 [TStoredProcedureIDGenerator] 表来生成 ID。[TStoredProcedureIDGenerator] 的定义如下:
创建数据库时,其初始化方式如下:
存储过程 [InsertStudentsByStoredProcedure] 的创建方式如下:
CREATE PROCEDURE [dbo].[InsertStudentsByStoredProcedure]
@Students AS StudentTableType READONLY
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE @FirstID AS INT
DECLARE @Count AS INT
SELECT @Count = COUNT(*) FROM @Students
BEGIN TRANSACTION
BEGIN TRY
SELECT @FirstID = NextId
FROM TStoredProcedureIDGenerator
WHERE TableName = 'TStudent_StoredProcedure'
UPDATE TStoredProcedureIDGenerator
SET NextId = @FirstID + @Count
WHERE TableName = 'TStudent_StoredProcedure'
INSERT INTO TStudent_StoredProcedure
SELECT Id + @FirstID, LastName, FirstName, Score
FROM @Students
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION
RAISERROR ('Unable to save students', 16, 1)
RETURN
END CATCH
IF @@TRANCOUNT > 0
COMMIT TRANSACTION
SELECT @FirstID AS FirstId
END
存储过程使用的用户定义表类型 [StudentTableType] 的定义如下:
CREATE TYPE [dbo].[StudentTableType] AS TABLE(
[Id] [int] NOT NULL,
[LastName] [varchar](50) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[Score] [int] NOT NULL
)
示例 MVC Web 应用程序
与本文 第一部分 相同,示例应用程序是使用 Visual Studio 2010 和 MVC 2 开发的,因此大多数人都可以下载并运行它,而不会遇到任何环境问题。
- Controllers\HomeController.cs 是应用程序的控制器。
- ViewModels\IndexVM.cs 实现 MVC 视图模型。
- "DataAccessLayer" 中的文件实现了使用每种方法访问数据库的代码。
DataAccessUtilities 文件夹中的文件设置了 NHibernate 环境。它还包含一些辅助类,用于通过 ADO.NET 访问数据库。在本文 第一部分,我详细介绍了如何在 NHibernate 中设置环境,因此在本部分我将不再讨论 DataAccessUtilities 文件夹中的文件。DataAccessMethod.cs 文件包含一个 enum,用于表示我们将要比较的三种方法。
namespace SQLPerformanceEntityInsert.DataAccessUtilities
{
namespace SQLPerformanceRead.DataAccessUtilities
{
public enum DataAccessMethod { AdoNet, NHibernateIdentity, NHibernateHilo };
}
}
视图模型
应用程序的视图模型实现在 ViewModels\IndexVM.cs 文件中。
using System;
using System.Collections.Generic;
using SQLPerformanceEntityInsert.DataAccessUtilities.SQLPerformanceRead.DataAccessUtilities;
namespace SQLPerformanceEntityInsert.ViewModels
{
public class Student
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public int Score { get; set; }
}
public class IndexVm
{
public DataAccessMethod AccessMethod { get; set; }
public TimeSpan? TimeSpent { get; set; }
public List<Student> UploadedStudents { get; set; }
}
}
Student
类代表上传到 Web 应用程序的学生,而 IndexVm
类将由 UI 使用,以显示上传的学生以及每种数据访问方法将学生插入数据库所花费的时间。
数据访问层
ADO.NET 数据访问方法
文件 ADONET\AdoNetDbAccess.cs 实现使用 ADO.NET 将学生插入数据库的代码。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Web;
using System.Linq;
using System.Xml.Linq;
using SQLPerformanceEntityInsert.DataAccessUtilities.ADONET;
using SQLPerformanceEntityInsert.ViewModels;
namespace SQLPerformanceEntityInsert.DataAccessLayer.ADONET
{
public static class AdoNetDbAccess
{
private static DataTable GetStudentList(HttpPostedFileBase fileStudents)
{
var table = new DataTable();
table.Columns.Add("Id", System.Type.GetType("System.Int32"));
table.Columns.Add("LastName", System.Type.GetType("System.String"));
table.Columns.Add("FirstName", System.Type.GetType("System.String"));
table.Columns.Add("Score", System.Type.GetType("System.Int32"));
var streamStudents = fileStudents.InputStream;
streamStudents.Position = 0;
var doc = XDocument.Load(streamStudents);
var students = doc.Descendants("student");
var i = 0;
foreach(var student in students)
{
var row = table.NewRow();
row["Id"] = i;
row["LastName"] = student.Descendants("LastName").First().Value;
row["FirstName"] = student.Descendants("FirstName").First().Value;
row["Score"] = student.Descendants("Score").First().Value;
table.Rows.Add(row);
i++;
}
return table;
}
public static IndexVm UploadFile(HttpPostedFileBase fileStudents)
{
var vm = new IndexVm();
var startTime = DateTime.Now;
vm.TimeSpent = DateTime.Now.Subtract(startTime);
var cmd = new SqlCommand
{
CommandType = CommandType.StoredProcedure,
CommandText = "InsertStudentsByStoredProcedure"
};
var table = GetStudentList(fileStudents);
cmd.Parameters.AddWithValue("@Students", table);
var idT = AdoNetUtility.GetADataTable(cmd);
var firstId = Convert.ToInt32(idT.Rows[0]["FirstId"]);
vm.TimeSpent = DateTime.Now.Subtract(startTime);
vm.UploadedStudents
= (from s in table.AsEnumerable()
select new Student()
{
Id = Convert.ToInt32(s["Id"]) + firstId,
LastName = Convert.ToString(s["LastName"]),
FirstName = Convert.ToString(s["FirstName"]),
Score = Convert.ToInt32(s["Score"]) + firstId
}).ToList();
return vm;
}
}
}
UploadFile
方法调用存储过程 [InsertStudentsByStoredProcedure],将学生作为单个批次插入到 [TStudent_StoredProcedure] 表中。事务在存储过程内部控制,因此如果成功,所有学生都将被插入。如果事务失败,则没有学生被插入。
使用标识 ID 字段的 NHibernate 数据访问方法
NHibernateIdentity\DataModel\TStudent_Identity.cs 定义了将学生插入 [TStudent_Identity] 表的数据模型。
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity.DataModel
{
public class TStudent_Identity
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual int Score { get; set; }
}
}
TStudent_Identity
类的 NHibernate 映射定义在 TableMapping\TStudentIdentityMap.cs 文件中。
using FluentNHibernate.Mapping;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity.DataModel;
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity.TableMapping
{
public class TStudentIdentityMap : ClassMap<TStudent_Identity>
{
public TStudentIdentityMap()
{
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.LastName).Column("LastName");
Map(x => x.FirstName).Column("FirstName");
Map(x => x.Score).Column("Score");
}
}
}
NHibernateIdentity\NHibernateIdentityDbAccess.cs 文件实现了在 NHibernate 映射之上将学生插入 [TStudent_Identity] 表的代码。
using System;
using System.Data;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using System.Collections.Generic;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity.DataModel;
using SQLPerformanceEntityInsert.DataAccessUtilities.NHibernate;
using SQLPerformanceEntityInsert.ViewModels;
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity
{
public static class NHibernateIdentityDbAccess
{
private static List<TStudent_Identity> GetStudentList(HttpPostedFileBase fileStudents)
{
var streamStudents = fileStudents.InputStream;
streamStudents.Position = 0;
var doc = XDocument.Load(streamStudents);
var students = (from s in doc.Descendants("student")
select new TStudent_Identity()
{
LastName = s.Descendants("LastName").First().Value,
FirstName = s.Descendants("FirstName").First().Value,
Score = Convert.ToInt16(s.Descendants("Score").First().Value)
}).ToList();
return students;
}
public static IndexVm UploadFile(HttpPostedFileBase fileStudents)
{
var vm = new IndexVm();
var startTime = DateTime.Now;
var students = GetStudentList(fileStudents);
using (var session = NHibernateUtility.OpenSession())
{
using (var transaction = session.BeginTransaction(IsolationLevel.Serializable))
{
foreach (var student in students)
{
session.Save(student);
}
transaction.Commit();
}
}
vm.TimeSpent = DateTime.Now.Subtract(startTime);
vm.UploadedStudents
= (from student in students
select new Student()
{
Id = student.Id,
LastName = student.LastName,
FirstName = student.FirstName,
Score = student.Score
}).ToList();
return vm;
}
}
}
NHibernate Hilo 数据访问方法
NHibernateHilo\DataModel\TStudent_Hilo.cs 定义了将学生插入 [TStudent_Hilo] 表的数据模型。
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo.DataModel
{
public class TStudent_Hilo
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual int Score { get; set; }
}
}
TStudent_Hilo
类的 NHibernate 映射定义在 TableMapping\TStudentHiloMap.cs 文件中。
using FluentNHibernate.Mapping;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo.DataModel;
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo.TableMapping
{
public class TStudentHiloMap : ClassMap<TStudent_Hilo>
{
public TStudentHiloMap()
{
Id(x => x.Id).Column("Id").GeneratedBy.HiLo("THiloIDGenerator", "Hi",
"100", "TableName = 'TStudent_Hilo'");
Map(x => x.LastName).Column("LastName");
Map(x => x.FirstName).Column("FirstName");
Map(x => x.Score).Column("Score");
}
}
}
当使用 NHibernate Hilo 时,我们需要在映射 "Id" 列时指定用于生成学生 ID 的表。NHibernateHilo\NHibernateHiloDbAccess.cs 文件实现了使用 NHibernate Hilo 将学生插入 [TStudent_Hilo] 表的代码。
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo.DataModel;
using SQLPerformanceEntityInsert.DataAccessUtilities.NHibernate;
using SQLPerformanceEntityInsert.ViewModels;
namespace SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo
{
public static class NHibernateHiloDbAccess
{
private static List<TStudent_Hilo> GetStudentList(HttpPostedFileBase fileStudents)
{
var streamStudents = fileStudents.InputStream;
streamStudents.Position = 0;
var doc = XDocument.Load(streamStudents);
var students = (from s in doc.Descendants("student")
select new TStudent_Hilo()
{
LastName = s.Descendants("LastName").First().Value,
FirstName = s.Descendants("FirstName").First().Value,
Score = Convert.ToInt16(s.Descendants("Score").First().Value)
}).ToList();
return students;
}
public static IndexVm UploadFile(HttpPostedFileBase fileStudents)
{
var vm = new IndexVm();
var startTime = DateTime.Now;
var students = GetStudentList(fileStudents);
using (var session = NHibernateUtility.OpenSession())
{
using (var transaction = session.BeginTransaction(IsolationLevel.Serializable))
{
foreach (var student in students)
{
session.Save(student);
}
transaction.Commit();
}
}
vm.TimeSpent = DateTime.Now.Subtract(startTime);
vm.UploadedStudents
= (from student in students
select new Student()
{
Id = student.Id,
LastName = student.LastName,
FirstName = student.FirstName,
Score = student.Score
}).ToList();
return vm;
}
}
}
要使用 NHibernate 或 NHibernate Hilo 将数据插入数据库,我们需要在代码中初始化事务,并在所有学生插入完成后提交它。
控制器
此 MVC 应用程序的控制器实现在 Controllers\HomeController.cs 文件中。
using System.Web;
using System.Web.Mvc;
using SQLPerformanceEntityInsert.DataAccessLayer.ADONET;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateHilo;
using SQLPerformanceEntityInsert.DataAccessLayer.NHibernateIdentity;
using SQLPerformanceEntityInsert.DataAccessUtilities.SQLPerformanceRead.DataAccessUtilities;
using SQLPerformanceEntityInsert.ViewModels;
namespace SQLPerformanceEntityInsert.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var vm = new IndexVm
{
AccessMethod = DataAccessMethod.AdoNet,
TimeSpent = null,
UploadedStudents = null
};
return View(vm);
}
[HttpPost]
public ActionResult UploadStudents(DataAccessMethod radAccessMethod,
HttpPostedFileBase fileStudents)
{
var vm = (radAccessMethod == DataAccessMethod.AdoNet)
? AdoNetDbAccess.UploadFile(fileStudents)
: (radAccessMethod == DataAccessMethod.NHibernateIdentity)
? NHibernateIdentityDbAccess.UploadFile(fileStudents)
: NHibernateHiloDbAccess.UploadFile(fileStudents);
vm.AccessMethod = radAccessMethod;
return View("Index", vm);
}
}
}
Index
操作方法仅加载应用程序的主页面。当文件上传时,UploadStudents
方法使用所选的数据访问方法将学生插入数据库。它还将上传的学生以及插入学生所花费的时间传递给视图页面进行显示。
View
示例应用程序是一个单视图 MVC 应用程序。视图实现在 "Views\Home\Index.aspx" 文件中。
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<SQLPerformanceEntityInsert.ViewModels.IndexVm>" %>
<%@ Import Namespace="SQLPerformanceEntityInsert.DataAccessUtilities.SQLPerformanceRead.DataAccessUtilities" %>
<asp:Content ID="MainContent" ContentPlaceHolderID="MainContent" runat="server">
<form id="frmMain" method="POST" enctype="multipart/form-data">
<div>
<label class="textLabel">Data access method</label>
<span style="margin-right: 10px">
<input type="radio" name="radAccessMethod"
value="<%=DataAccessMethod.AdoNet.ToString() %>"
<% if (Model.AccessMethod == DataAccessMethod.AdoNet)
{ %> checked="checked" <% } %> />
<label>ADO.Net</label>
<input type="radio" name="radAccessMethod"
value="<%=DataAccessMethod.NHibernateIdentity.ToString() %>"
<% if (Model.AccessMethod == DataAccessMethod.NHibernateIdentity)
{ %> checked="checked" <% } %> />
<label>NHibernate Identity</label>
<input type="radio" name="radAccessMethod"
value="<%=DataAccessMethod.NHibernateHilo.ToString() %>"
<% if (Model.AccessMethod == DataAccessMethod.NHibernateHilo)
{ %> checked="checked" <% } %> />
<label>NHibernate Hilo</label>
</span>
<label class="textLabel">File to upload</label>
<span>
<input type="file" id="fileStudents" name="fileStudents"/>
<input type="button" id="btnSubmitFile" value="Submit file" />
</span>
</div>
</form>
<%if (Model.TimeSpent != null)
{ %>
<br/>
<label>Database Access Time: <span style="color: red">
<%=Model.TimeSpent%></span></label>
<% } %>
<%if (Model.UploadedStudents != null)
{%>
<div id="divStudentSummary" class="divTable">
<div class="tableHeader">
<span>StudentId</span>
<span>Last Name</span>
<span>First Name</span>
<span>Score</span>
</div>
<%foreach (var student in Model.UploadedStudents)
{%>
<div>
<span><%=student.Id %></span>
<span><%=student.LastName %></span>
<span><%=student.FirstName %></span>
<span><%=student.Score%></span>
</div>
<% } %>
</div>
<% } %>
</asp:Content>
此 MVC 视图负责上传 XML 文件并显示已插入学生的結果。此视图使用的 JavaScript 代码如下:
<script type="text/javascript">
$(document).ready(function () {
$('#btnSubmitFile').click(function () {
if ($.trim($('#fileStudents').val()) == '') {
alert('Please browse a file to upload ...');
return;
}
$('#frmMain').attr('action', fileUploadUrl).submit();
});
});
</script>
运行应用程序
请确保您的 SQL Server 正在运行且可访问,并确保 [SQLPerformanceInsert] 数据库已成功创建。然后我们可以运行应用程序。
我们可以选择一个数据访问方法,然后将文件上传到服务器。图片显示了使用 NHibnerate Identity 方法的结果。
比较结果
以上结果是通过运行 10 次取平均值来计算插入 5000 名学生所花费的时间。我们可以看到,NHibernate Hilo 的运行速度确实比 NHibernate Identity 方法快 11 倍。但它比直接 ADO.NET 方法慢,而直接 ADO.NET 方法比 NHibernate Identity 方法快 592 倍。
NHibernate Hilo 的一个附加问题
当示例数据库首次创建时,我们按如下方式初始化了 [THiloIDGenerator] 表:
插入 5000 名学生后,[THiloIDGenerator] 表的结果如下:
Hi 值为 50 是合理的,因为我们插入了 5000 名学生,并且每 100 名学生,我们将 Hi 值增加 1。但在插入 30000 名学生后,我得到以下结果:
Hi 值 298 是什么意思?我尝试在互联网上搜索 NHibernate Hilo 生成 ID 的算法,但没有找到任何有用的信息。无论如何,如果我们选择使用 NHibernate Hilo 将数据插入数据库,我们会遇到以下问题:
- 如果多个应用程序尝试向同一个数据库表插入数据,并且其中一个应用程序使用 NHibernate Hilo,那么它将使其他应用程序别无选择。它们必须使用 NHibernate Hilo,并且必须在 Hibernate 映射中指定相同的 Hilo 值。否则,不同的应用程序可能会生成重复的 ID。当其中一个应用程序需要快速响应时间,但又被迫使用较慢的 NHibernate Hilo 方法时,这是一个问题。
- 如果出于某种原因,数据库管理员需要直接向表中添加单个记录以进行紧急修复,并且该表正被某个应用程序通过 NHibernate Hilo 使用,那么他需要弄清楚 Hilo 算法来创建他的记录 ID。即使他找到了正确的 ID,他也需要获取 100 个 ID(如本示例中配置的),并只使用其中 1 个,导致浪费 99 个 ID,如果他不保留他的 ID 记录的话,这对他本已繁忙的工作来说是一项额外的职责。
关注点
- 这是关于 .NET 环境中数据访问方法比较的第二部分。
- 在本文的这一部分,我们对向数据库表中插入数据的性能进行了比较。比较表明,在不使用 NHibernate 的情况下,我们可以通过存储过程实现非常好的性能。如果我们使用 NHibernate 并通过标识字段生成数据记录的 ID,性能会非常差。尽管 NHibernate Hilo 显示了一些改进,但它仍然比根本不使用 NHibernate 的方法慢得多。
- NHibernate Hilo 相对于使用标识字段的 NHibernate 方法确实显示出一些性能改进,但它也带来了一些其他问题,一旦应用程序部署到生产环境,这些问题可能成为巨大的维护难题。
- 如果您对此类主题不熟悉,示例应用程序可以向您展示如何使用这些方法将数据插入数据库。
- 如果您对此主题感兴趣,可以查看本文的 第三部分。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
- 首次修订 - 2013 年 8 月 1 日。