Rollback when retrieving datatable through DataAdapter - rollback

I have several methods to connect to a SQL Server database. One of them is for retrieving a datatable through a stored procedure. In some instances I use this one where the stored procedure does an actual UPDATE to the database and at the end I just want to retrieve multiple values back, hence retrieving a datatable instead of an output parameter.
I noticed that if an error happens during the UPDATE statement, there is no automatic rollback.
So, I've added the ROLLBACK function in my CATCH statement:
Public Function StoredProcReturnTable(ByVal colParameters As Collection, _
ByVal strProcName As String, ByVal strTableName As String, _
ByVal strServer As String, ByVal strDatabase As String) As Data.DataTable
Dim cn As SqlClient.SqlConnection
Dim cm As SqlClient.SqlCommand = New SqlClient.SqlCommand
Dim objParameter As SqlClient.SqlParameter
Dim ds As New DataSet
Dim da As SqlClient.SqlDataAdapter
Try
With cm
.CommandType = CommandType.StoredProcedure
.CommandTimeout = CommandTimeout
.CommandText = strProcName
For Each objParameter In colParameters
.Parameters.Add(objParameter)
Next
cn = GetCN(strServer, strDatabase)
.Connection = cn
cm.Transaction = cn.BeginTransaction
da = New SqlClient.SqlDataAdapter(cm)
da.Fill(ds)
cm.Transaction.Commit()
ds.Tables(0).TableName = strTableName
Return ds.Tables(0)
End With
Catch ex As Exception
cm.Transaction.Rollback()
Throw ex
Finally
Close(cn)
End Try
End Function
When testing the stored procedure with an exception, I noticed that my Debug Output window shows:
Step into: Stepping over non-user code 'System.Data.SqlClient.SqlCommand.RunExecuteReaderTds'
Step into: Stepping over non-user code 'System.Data.SqlClient.SqlCommand.ExecuteReader'
Step into: Stepping over non-user code 'System.Data.Common.DbDataAdapter.FillInternal'
Step into: Stepping over non-user code 'System.Data.Common.DbDataAdapter.FillInternal'
Step into: Stepping over non-user code 'System.Data.Common.DbDataAdapter.FillInternal'
Step into: Stepping over non-user code 'System.Data.Common.DbDataAdapter.Fill'
Step into: Stepping over non-user code 'System.Data.Common.DbDataAdapter.Fill'
I confirmed that the rollback was working, but I am worried that because of those messages that I am doing something wrong? I don't get these messages when using ExecuteNonQuery() for my regular INSERT/UPDATE/DELETE stored procedures.

Related

"Guid should contain 32 digits" serilog error with sql server sink

I am getting this error occasionally with the MSSQLServer sink. I can't see what's wrong with this guid. Any ideas? I've verified in every place I can find the data type of the source guid is "Guid" not a string. I'm just a bit mystified.
Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).Couldn't store <"7526f485-ec2d-4ec8-bd73-12a7d1c49a5d"> in UserId Column. Expected type is Guid.
The guid in this example is:
7526f485-ec2d-4ec8-bd73-12a7d1c49a5d
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
seems to match the template to me?
Further details:
This is an occasional issue, but when it arises it arises a lot. It seems to be tied to specific Guids. Most Guids are fine, but a small subset have this issue. Our app logs thousands of messages a day, but these messages are not logged (because of the issue) so it is difficult for me to track down exactly where the specific logs that are causing this error come from. However, we use a centralized logging method that is run something like this. This test passes for me, but it mirrors the setup and code we use for logging generally, which normally succeeds. As I said, this is an intermittent issue:
[Fact]
public void Foobar()
{
// arrange
var columnOptions = new ColumnOptions
{
AdditionalColumns = new Collection<SqlColumn>
{
new SqlColumn {DataType = SqlDbType.UniqueIdentifier, ColumnName = "UserId"},
},
};
columnOptions.Store.Remove(StandardColumn.MessageTemplate);
columnOptions.Store.Remove(StandardColumn.Properties);
columnOptions.Store.Remove(StandardColumn.LogEvent);
columnOptions.Properties.ExcludeAdditionalProperties = true;
var badGuid = new Guid("7526f485-ec2d-4ec8-bd73-12a7d1c49a5d");
var connectionString = "Server=(localdb)\\MSSQLLocalDB;Database=SomeDb;Trusted_Connection=True;MultipleActiveResultSets=true";
var logConfiguration = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.MSSqlServer(connectionString, "Logs",
restrictedToMinimumLevel: LogEventLevel.Information, autoCreateSqlTable: false,
columnOptions: columnOptions)
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information);
Log.Logger = logConfiguration.CreateLogger();
// Suspect the issue is with this line
LogContext.PushProperty("UserId", badGuid);
// Best practice would be to do something like this:
// using (LogContext.PushProperty("UserId", badGuid)
// {
Log.Logger.Information(new FormatException("Foobar"),"This is a test");
// }
Log.CloseAndFlush();
}
One thing I have noticed since constructing this test code is that the "PushProperty" for the UserId property is not captured and disposed. Since behaviour is "undefined" in this case, I am inclined to fix it anyway and see if the problem goes away.
full stack:
2020-04-20T08:38:17.5145399Z Exception while emitting periodic batch from Serilog.Sinks.MSSqlServer.MSSqlServerSink: System.ArgumentException: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).Couldn't store <"7526f485-ec2d-4ec8-bd73-12a7d1c49a5d"> in UserId Column. Expected type is Guid.
---> System.FormatException: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
at System.Guid.GuidResult.SetFailure(Boolean overflow, String failureMessageID)
at System.Guid.TryParseExactD(ReadOnlySpan`1 guidString, GuidResult& result)
at System.Guid.TryParseGuid(ReadOnlySpan`1 guidString, GuidResult& result)
at System.Guid..ctor(String g)
at System.Data.Common.ObjectStorage.Set(Int32 recordNo, Object value)
at System.Data.DataColumn.set_Item(Int32 record, Object value)
--- End of inner exception stack trace ---
at System.Data.DataColumn.set_Item(Int32 record, Object value)
at System.Data.DataRow.set_Item(DataColumn column, Object value)
at Serilog.Sinks.MSSqlServer.MSSqlServerSink.FillDataTable(IEnumerable`1 events)
at Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(IEnumerable`1 events)
at Serilog.Sinks.PeriodicBatching.PeriodicBatchingSink.OnTick()
RESOLUTION
This issue was caused because someone created a log message with a placeholder that had the same name as our custom data column, but was passing in a string version of a guid instead of one typed as a guid.
Very simple example:
var badGuid = "7526f485-ec2d-4ec8-bd73-12a7d1c49a5d";
var badGuidConverted = Guid.Parse(badGuid); // just proving the guid is actually valid.
var goodGuid = Guid.NewGuid();
using (LogContext.PushProperty("UserId",goodGuid))
{
Log.Logger.Information("This is a problem with my other user {userid} that will crash serilog. This message will never end up in the database.", badGuid);
}
The quick fix is to edit the message template to change the placeholder from {userid} to something else.
Since our code was centralized around the place where the PushProperty occurs, I put some checks in there to monitor for this and throw a more useful error message in the future when someone does this again.
I don't see anything obvious in the specific code above that would cause the issue. The fact that you call PushProperty before setting up Serilog would be something I would change (i.e. set up Serilog first, then call PushProperty) but that doesn't seem to be the root cause of the issue you're having.
My guess, is that you have some code paths that are logging the UserId as a string, instead of a Guid. Serilog is expecting a Guid value type, so if you give it a string representation of a Guid it won't work and will give you that type of exception.
Maybe somewhere in the codebase you're calling .ToString on the UserId before logging? Or perhaps using string interpolation e.g. Log.Information("User is {UserId}", $"{UserId}");?
For example:
var badGuid = "7526f485-ec2d- 4ec8-bd73-12a7d1c49a5d";
LogContext.PushProperty("UserId", badGuid);
Log.Information(new FormatException("Foobar"), "This is a test");
Or even just logging a message with the UserId property directly:
var badGuid = "7526f485-ec2d-4ec8-bd73-12a7d1c49a5d";
Log.Information("The {UserId} is doing work", badGuid);
Both snippets above would throw the same exception you're having, because they use string values rather than real Guid values.

Stored procedure returning dynamic columns in Entity Framework

I have a function calling a SQL Server stored procedure using Entity Framework 6.2.
The stored procedure returns a result set which has different number of columns on each call, and column names may vary on each call.
Function getListOfDocs() As JsonResult
Try
Using entities As PromatEntities = New PromatEntities()
Dim param(1) As SqlParameter
param(0) = New SqlParameter("#ProjID", SqlDbType.Int)
param(0).Value = vProjectId
Dim query = entities.Database.SqlQuery(Of "help required here")("sp_EIP_IPSSDocMaster_Get", param) // cannot handle this case as entity framework needs type
Dim lstDocs = query.ToList
End Using
Return Json(New With {lstDocs}, JsonRequestBehavior.AllowGet)
Catch ex As Exception
ClsCommon.ExceptionManager(ex)
Return Nothing
End Try
End Function
But Entity Framework doesn't allow anonymous types in database.SqlQuery. Can anyone suggest a way to solve the issue and get the anonymous type data to view?

Executing a SQL Server stored procedure from a historian calculation

Using Historian 5.5 and SQL Server 2012.
I have a stored procedure in SQL Server called perfEng_RWtopits and I want to call this procedure from within a calculation tag in Historian Administrator.
The stored procedure returns one float value.
I have the following code so far:
Dim sql
Dim con
Dim cmd
Dim value
sql = "perfEng_RWtopits"
Set con = CreateObject("ADODB.Connection")
con.ConnectionString = "myconnectionstring"
con.Open
Set cmd = CreateObject("ADODB.Command")
cmd.ActiveConnection = con
cmd.CommandText = sql
value = cmd.Execute
con.close
Set cmd = Nothing
Set con = Nothing
When I test the calculation I get a value of zero and a quality of bad. If I execute the stored procedure within SQL Server I get 17.123554 (which is correct). Also if I add the following to the end.
Result = value
I get the following error message.
Can anyone help?
You have an error in your vb script. To bad that we can't debug that in Historian Administrator in calcullation tab.
When using a parameterless function you can execute it by writing:
FunctionName
or
FunctionName(), but when you want to pass the result to a variable then you must use () like this value = FunctionName().
You are using function cmd.Execute without () and returning the result to value. Just fix the line
value = cmd.Execute
to
value = cmd.Execute()

How to check whether stored procedure executed successfully

If you have a stored procedure that executing multiple queries, doesn't return values and doesn't have error-handling in it, how can you ascertain whether the stored procedure ended successfully when calling from VB code?
For example, if the stored procedure is something like:
create stored procedure some_procedure
Insert into ...
delete ...
update ...
end
... and the VB code is like this:
Cmd.ActiveConnection = cn
Cmd.CommandText = "some_procedure"
Cmd.CommandType = adCmdStoredProc
Set rs = Cmd.Execute
Debug.Print rs(0)
... then how can I get a return value that is 0 for success or 1 for failure?
I believe passing the optional RecordsAffected parameter with the Execute statement will work for you. Since you're so scarce on the details you'll have to try it, but this has worked for me with SQL, and FoxPro OLEDB providers. Also, why no error handling? IMHO not having an error handler in code that has a good possibility of producing an error is asking for trouble.
...
Dim lngRecsAffctd As Long
Cmd.ActiveConnection = cn
Cmd.CommandText = "some_procedure"
Cmd.CommandType = adCmdStoredProc
Cmd.Execute lngRecsAffctd, ,adCmdStoredProc 'why set to a recordset when the sproc doesn't return anything?
Debug.Print lngRecsAffctd
...
The complete details of the Execute command and the RecordsAffected parameter can be found at msdn.microsoft.com/en-us/library/ms681559%28v=vs.85%29.aspx

Why can't I get anything back when I use Linq to Sql Select?

Could you please check my code? Why can't I get any values back when I use Linq to Sql?
BHS_TimeSheet is my database table in which have some records.
Model.TimeSheet is a class I create in the model.
Private db As DataFactoryDataContext
Public Sub New()
db = New DataFactoryDataContext
End Sub
Public Sub New(ByVal repository As DataFactoryDataContext)
db = repository
End Sub
Public Function GetTimeSheetByProject(ByVal wbs1 As String, ByVal wbs2 As String, ByVal wbs3 As String) _
As List(Of Model.TimeSheet) Implements ITimeSheetRepository.GetTimeSheetByProject
Return (From ts In db.BHS_TimeSheets _
Where ts.WBS1.Equals(wbs1) And ts.WBS2.Equals(wbs2) And ts.WBS3.Equals(wbs3) _
Select New Model.TimeSheet(ts.TSBatchNo, ts.Employee, ts.TransDate, ts.WBS1, ts.WBS2, ts.WBS3, ts.LaborCode, _
ts.RegHrs, ts.OvtHrs, ts.SpecialOvtHrs, ts.TransComment, ts.Status, ts.AuthorizedBy, _
ts.RejectReason, ts.ModDate)).ToList
End Function
Your Linq-to-SQL statement
(From ts In db.BHS_TimeSheets
Where ts.WBS1.Equals(wbs1)
And ts.WBS2.Equals(wbs2)
And ts.WBS3.Equals(wbs3) _
basically corresponds to this SQL query:
SELECT * FROM dbo.BHS_TimeSheets
WHERE WBS1 = (value for wbs1)
AND WBS2 = (value for wbs2)
AND WBS3 = (value for wbs3)
Does that SQL query return any values, if you call it with in SQL Server Management Studio using the same parameters for wbs1, wbs2, wbs3 as you do in your Linq-to-SQL code??
Update: okay, so the SQL query does return results - next step: approach the Linq-to-SQL stuff step by step. First, try this - do you get any results??
Dim basicQueryResults = (From ts In db.BHS_TimeSheets
Where ts.WBS1.Equals(wbs1)
And ts.WBS2.Equals(wbs2)
And ts.WBS3.Equals(wbs3)
).ToList();
Does your resulting list of items have a .Count > 0 or not??
If not: there must be something wrong with your Linq-to-SQL model then. Have you changed your database and not updated the DBML file?? Can you drop the DBML file and do it again - does it work now? Or do you still have the same results??

Resources