问题
Consider the following TestDb with TestTable and Procedure
USE TestDb
GO
--DROP TABLE dbo.TestTable
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = 'TestTable')
BEGIN
CREATE TABLE dbo.TestTable
(
RecordId int NOT NULL IDENTITY(1,1) PRIMARY KEY
, StringValue varchar(50) NULL
, DateValue date NULL
, DateTimeValue datetime NULL
, MoneyValue money NULL
, DecimalValue decimal(19,4) NULL
, IntValue int NULL
, BitValue bit NOT NULL
)
INSERT INTO dbo.TestTable
SELECT 'Test', CAST(GETDATE() AS DATE), GETDATE(), 100.15, 100.0015, 100, 1
UNION SELECT NULL, NULL, NULL, NULL, NULL, NULL, 0
END
GO
IF EXISTS (SELECT 1 FROM sys.procedures WHERE name = 'Get_TestTable')
DROP PROCEDURE dbo.Get_TestTable
GO
CREATE PROCEDURE dbo.Get_TestTable (@RecordId int = NULL) AS WAITFOR DELAY '00:00:30'; SELECT * FROM dbo.TestTable WHERE RecordId = ISNULL(@RecordId,RecordId);
GO
EXEC dbo.Get_TestTable @RecordId = NULL
When using WebMatrix built-in database query helper, you can do the following:
@{
string errorMessage = String.Empty;
int? RecordId = null;
IEnumerable<dynamic> rowsTestTable = null;
try
{
using (Database db = Database.Open("TestDb"))
{
rowsTestTable = db.Query("EXEC dbo.Get_TestTable @RecordId=@0",RecordId);
}
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
@if(errorMessage == String.Empty)
{
<table border="1">
<thead>
<tr>
<th>RecordId</th>
<th>StringValue</th>
<th>DateValue</th>
<th>DateTimeValue</th>
<th>MoneyValue</th>
<th>DecimalValue</th>
<th>IntValue</th>
<th>BitValue</th>
</tr>
</thead>
<tbody>
@foreach(var row in rowsTestTable)
{
<tr>
<td>@row["RecordId"]</td>
<td>@row["StringValue"]</td>
<td>@if(@row["DateValue"] != null){@Html.Raw(String.Format("{0:MM/dd/yyyy}",@row["DateValue"]));}</td>
<td>@if(@row["DateTimeValue"] != null){@Html.Raw(String.Format("{0:MM/dd/yyyy hh:mm:ss.fff tt}",@row["DateTimeValue"]));}</td>
<td>@if(@row["MoneyValue"] != null){@Html.Raw(String.Format("{0:c}",@row["MoneyValue"]));}</td>
<td>@row["DecimalValue"]</td>
<td>@row["IntValue"]</td>
<td>@row["BitValue"]</td>
</tr>
}
</tbody>
</table>
}
<p>@errorMessage</p>
<h4>No Additional Problem - On handling of DateValue</h4>
@try
{
foreach(var row in rowsTestTable)
{
<p>@if(row.DateValue != null){@Html.Raw(DateTime.Parse(row.DateValue.ToString()))}</p>
}
}
catch (Exception ex)
{
<p>@ex.Message</p>
}
<h4>No Additional Problem - On handling of MoneyValue (and other number values)</h4>
@try
{
foreach(var row in rowsTestTable)
{
<p>@if(row.MoneyValue != null){@Html.Raw(Double.Parse(row.MoneyValue.ToString()))}</p>
}
}
catch (Exception ex)
{
<p>@ex.Message</p>
}
</body>
</html>
This gives a Timeout expired error because the WebMatrix Database.Query helper has fixed default 30 second CommandTimeout. Is there any way to override the default for an individual query to something like 5 minutes?
Not having found a solution, I went down the road of creating my own SimpleQuery helper based on numerous searches and trying out things until I finally found a code reference that I was able to understand and adapt.
using System.Collections.Generic; // IEnumerable<dynamic>
using System.Data; // IDataRecord
using System.Data.SqlClient; // SqlConnection
using System.Dynamic; // DynamicObject
public class SimpleQuery
{
public static IEnumerable<dynamic> Execute(string connectionString, string commandString, int commandTimeout)
{
using (var connection = new SqlConnection(connectionString))
{
using (var command = new SqlCommand(commandString, connection))
{
command.CommandTimeout = commandTimeout;
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
foreach (IDataRecord record in reader)
{
yield return new DataRecordDynamicWrapper(record);
}
}
connection.Close();
}
}
}
public class DataRecordDynamicWrapper : DynamicObject
{
private IDataRecord _dataRecord;
public DataRecordDynamicWrapper(IDataRecord dataRecord) { _dataRecord = dataRecord; }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _dataRecord[binder.Name];
return result != null;
}
}
}
So now with the changes to the web-code to use the new SimpleQuery helper, I can get almost equivalent results, but with some problems
@{
string errorMessage = String.Empty;
int? RecordId = null;
IEnumerable<dynamic> rowsTestTable = null;
try
{
string commandString = String.Format("dbo.Get_TestTable @RecordId={0}", RecordId == null ? "null" : RecordId.ToString()); // Problem 1: Have to use String.Format to embed the Parameters
rowsTestTable = SimpleQuery.Execute(System.Configuration.ConfigurationManager.ConnectionStrings["TestDb"].ConnectionString,commandString,300);
foreach(var row in rowsTestTable) { break; } // Problem 2: Have to force query execution here, so the error (if any) gets trapped here
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
@if(errorMessage == String.Empty)
{
<table border="1">
<thead>
<tr>
<th>RecordId</th>
<th>StringValue</th>
<th>DateValue</th>
<th>DateTimeValue</th>
<th>MoneyValue</th>
<th>DecimalValue</th>
<th>IntValue</th>
<th>BitValue</th>
</tr>
</thead>
<tbody>
@foreach(var row in rowsTestTable)
{
<tr>
@*<td>@row["RecordId"]</td>*@ <!-- Problem 3: Can't reference as row["FieldName"], so if any field names have spaces or other special characters, can't reference -->
<td>@row.RecordId</td>
<td>@row.StringValue</td>
<td>@if(@row.DateValue != null){@Html.Raw(String.Format("{0:MM/dd/yyyy}",@row.DateValue));}</td>
<td>@if(@row.DateTimeValue != null){@Html.Raw(String.Format("{0:MM/dd/yyyy hh:mm:ss.fff tt}",@row.DateTimeValue));}</td>
<td>@if(@row.MoneyValue != null){@Html.Raw(String.Format("{0:c}",@row.MoneyValue));}</td>
<td>@row.DecimalValue</td>
<td>@row.IntValue</td>
<td>@row.BitValue</td>
</tr>
}
</tbody>
</table>
}
<p>@errorMessage</p>
<h4>Additional Problem - Unexpected handling of DateValue</h4>
@try
{
foreach(var row in rowsTestTable)
{
<p>@if(row.DateValue != null){@Html.Raw(DateTime.Parse(row.DateValue.ToString()))}</p>
}
}
catch (Exception ex)
{
<p>@ex.Message</p>
}
<h4>Additional Problem - Unexpected handling of MoneyValue (and other number values)</h4>
@try
{
foreach(var row in rowsTestTable)
{
<p>@if(row.MoneyValue != null){@Html.Raw(Double.Parse(row.MoneyValue.ToString()))}</p>
}
}
catch (Exception ex)
{
<p>@ex.Message</p>
}
</body>
</html>
Problem 1-3 are commented in the second web-code which is using the SimpleQuery helper. These I can work around, but what I am still struggling with is why the NULL check isn't detected for the Number and Date values.
I would appreciate help to properly detect those, so I can avoid the subsequent error when using Double.Parse or DateTime.Parse. I would also appreciate any general pointers/improvements for either the SimpleQuery helper, or anything else you see.
Thanks in advance.
回答1:
You could try switching to using Dapper. It has a very similar syntax to WebMatrix.Data, can return results as IEnumerable<dynamic>
or strongly typed if you prefer, and allows you to override the command timeout on a per query basis.
https://github.com/StackExchange/dapper-dot-net
回答2:
Using the following when using my SimpleQuery Helper works on Detecting Null or String.Empty, because the values from my Helper when converted to ToString() are coming as String.Empty while coming from Database.Query, they come back as NULL.
@try
{
foreach(var row in rowsTestTable)
{
<p>@if(!String.IsNullOrEmpty(row.DateValue.ToString())){@Html.Raw(DateTime.Parse(row.DateValue.ToString()))}</p>
}
}
catch (Exception ex)
{
<p>@ex.Message</p>
}
So while this doesn't explain to me why there is a difference or how to make my SimpleQuery Helper more equivalent to Database.Query, it does help me move past the immediate problem.
来源:https://stackoverflow.com/questions/26471744/webmatrix-database-query-with-custom-commandtimeout