Get error messages yet continue execution in SMO - c#

I'm working on rewriting the app we use to run upgrades on our database. Basically the idea of the app is it takes a bunch of scripts (one for each version) and runs each script between the current version of the database and the most recent version it has. I'm trying to find a better way for us to handle this process and I tried to make SQL Management Objects work the way we need them. For reference, here are the limitations I have to work with.
It has to handle GO statements (which SMO does)
It can't require us to modify the files we have. (This will be used with hundreds, maybe thousands of files and we don't want to have to go edit each one of them manually, so adding try catches are kinda out of the question)
It has to continue to the next GO statement if it encounters an error. This is mostly to match the way our current app works. If an error is encountered in one of the batches of the script we want it to continue on to the next one since they are most of the time unrelated.
If the script encounters an error, it has to output an error message so the user can know a version's upgrade didn't work, and so the developers can correct the error for the next version (here is the problem)
Here's what I currently have as a code:
string messages = "";
private void button1_Click(object sender, EventArgs e)
{
string setup = File.ReadAllText(#"[redacted]\Setup.sql");
string script = File.ReadAllText(#"[redacted]\6.3.6002.0.sql");
string script2 = File.ReadAllText(#"[redacted]\6.3.6003.0.sql");
var cnx = new SqlConnection(/*proper connection string*/);
var server = new Server(new ServerConnection(cnx));
//server.ConnectionContext.InfoMessage += ConnectionContext_InfoMessage;
server.ConnectionContext.ServerMessage += ConnectionContext_ServerMessage;
server.ConnectionContext.ExecuteNonQuery(setup);
server.ConnectionContext.ExecuteNonQuery(script);
server.ConnectionContext.ExecuteNonQuery(script2, ExecutionTypes.ContinueOnError);
txtResult.Text = messages;
}
private void ConnectionContext_ServerMessage(object sender, ServerMessageEventArgs e)
{
messages += e.Error.Message + "\r\n";
}
And here are the scripts I'm using:
Setup.sql:
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'UPGRADE_HISTORY')
DROP TABLE UPGRADE_HISTORY
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'TEST_CODE_TABLE')
DROP TABLE TEST_CODE_TABLE
CREATE TABLE UPGRADE_HISTORY (
UPDATE_DATE DATE NOT NULL,
VERSION_TXT VARCHAR(50) NOT NULL,
PRIMARY KEY (UPDATE_DATE, VERSION_TXT)
)
CREATE TABLE TEST_CODE_TABLE (
CODE_VALUE INT PRIMARY KEY,
DESCRIPTION_TXT VARCHAR(250) NOT NULL
)
INSERT INTO UPGRADE_HISTORY VALUES
(DATEADD(d, -3, GETDATE()), '6.2.5000'),
(DATEADD(d, -1, GETDATE()), '6.2.5001'),
(DATEADD(d, -1, GETDATE()), '6.2.5002'),
(DATEADD(d, -1, GETDATE()), '6.3.6000.0'),
(DATEADD(d, -1, GETDATE()), '6.3.6001.0')
INSERT INTO TEST_CODE_TABLE VALUES
(1001, 'Test Code Table'),
(1002, 'Test Code Table 2')
6.3.6602.0.sql:
INSERT INTO UPGRADE_HISTORY VALUES
(GETDATE(), '6.3.6001.0')
GO
PRINT 'Test Code Table Change'
GO
UPDATE TEST_CODE_TABLE SET DESCRIPTION_TXT = 'Test Code Table Change' WHERE CODE_VALUE = 1002;
GO
6.3.6003.0.sql:
INSERT INTO UPGRADE_HISTORY VALUES
(GETDATE(), '6.3.6003.0')
GO
PRINT 'Test Error'
GO
INSERT INTO CODE_TABLE VALUES (1001, 'Test')
--This will throw an error since this will conflict with the primary key
--of the code table (or you know, because I just noticed it doesn't call
--the right table, it's really relevant since I want it to throw an
--error, w/e what it is)
GO
PRINT 'Second Test Code Table Change'
GO
UPDATE TEST_CODE_TABLE SET DESCRIPTION_TXT = 'Test Code Table Change 2' WHERE CODE_VALUE = 1002;
--We still want this to execute.
GO
This is to reproduce a situation that can happen in our updates. So, as it is, the setup is only to create recreate the database each time so I can use the same scripts, then the first upgrade file is to simulate a functioning as intended file, then finally the 2nd upgrade file is to simulate an upgrade file that has an error. And this is where the problems start. As I've got it working at the moment, when the second script is executed, the first part runs, then the second part runs and errors out, but I don't get an error message. Neither the InfoMessage nor the ServerMessage events get fired. Then the third part runs (the one after the statement that errors out), and I get a ServerMessage for the print. For reference, here's the output I'm receiving:
Test Code Table Change
Test Error
Second Test Code Table Change
The print before and after the errors happen, and I can confirm from double checking the data that the UPDATE statement after the error is also executed. However, no message or error is thrown for the fact that the INSERT statement throws an error. We would really need to have SMO throw an error, or trigger the ServerMessage event, or anything really. Is there something I'm missing, or is this a shortcoming of the framework.

Related

AseConnection InfoMessage returning print statement as an error message

I am working in Sybase ASE 15.7 and I am trying to return messages from a T-SQL as shown below. I'm using the AseConnection.InfoMessage event handler to capture messages from the database. It is working fine but it is also returning the print statement as an AseError message. Why is this happening and how can I correct it?
Console Output:
Log: 12522 rows in SomeTable
AseError Log: 12522 rows in SomeTable
string sql= #"set nocount on
declare #rowcount int
select #rowcount = count(*) from SomeTable
print 'Log: %1! rows in SomeTable', #rowcount";
using (var conn = GetOpenConnection(connectionString))
{
conn.InfoMessage += (s,e) =>
{
Console.WriteLine(e.Message);
foreach (var error in e.Errors)
Console.WriteLine(error.ToString())
};
using (var command = new AseCommand(sql, conn))
{
command.ExecuteNonQuery();
}
}
It is just a really bizarre design decision by the authors of the driver. All messages, errors and info, share a data structure that describes the message as an AseError.
I work on an open source project that produces a .NET Core version of the AseClient. One of the goals of that project is to be a drop-in replacement for the driver from SAP/Sybase - and for that reason we have replicated the above design decision - as weird as it is.
A severity >10 is considered an error. Otherwise it is considered informational. If it helps you make sense of it, here is our code for interpreting the server messages: MessageTokenHandler.cs

Walkthrough: Creating a simple data application by using ADO.NET

I am following tutorial on https://msdn.microsoft.com/en-us/library/jj943772.aspx
Has anyone finished this project?
When I am doing it, the buttons in navigation don't seem to respond...any help will be very welcome.
Another issue is as follows:-
I am sorry Fran Tan, I should have included the code. I thought someone has done it and can show me the right course. Indeed this happened so thank you Alex W. It was matter of double clicking on the buttons to create button click method. I would like to ask: The app has been build and published and when I try to enter a new customer it comes back with the message: 'Customer ID was not returned. Account could not be created'.: //NC-10 try-catch-finally
try
{
//NC-11 Open the connection.
conn.Open();
//NC-12 Run the stored procedure.
cmdNewCustomer.ExecuteNonQuery();
//NC-13 Customer ID is an IDENTITY value from the database.
this.parsedCustomerID = (int)cmdNewCustomer.Parameters["#CustomerID"].Value;
this.txtCustomerID.Text = Convert.ToString(parsedCustomerID);
}
catch
{
//NC-14 A simple catch.
MessageBox.Show("Customer ID was not returned. Account could not be created.");
}
finally
{
//NC-15 Close the connection.
conn.Close();
}
and this is the stored procedure:
CREATE PROCEDURE [Sales].[uspNewCustomer]
#CustomerName NVARCHAR (40),
#CustomerID INT OUTPUT
AS
BEGIN
INSERT INTO [Sales].[Customer] (CustomerName)
VALUES (#CustomerName);
SET #CustomerID = SCOPE_IDENTITY();
RETURN ##ERROR
END
GO

Devart ChangeConflictException but values still written to database

I have an intermittent Devart.Data.Linq.ChangeConflictException: Row not found or changed raising it's ugly head. The funny thing is, the change is still written to the database!
The stack trace says:
Devart.Data.Linq.ChangeConflictException: Row not found or changed.
at Devart.Data.Linq.Engine.b4.a(IObjectEntry[] A_0, ConflictMode A_1, a A_2)
at Devart.Data.Linq.Engine.b4.a(ConflictMode A_0)
at Devart.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at Devart.Data.Linq.DataContext.SubmitChanges()
at Billing.Eway.EwayInternal.SuccessCustomerRenewal(String username, Bill bill, EwayTransaction transaction) in c:\Users\Ian\Source\Repos\billing-class-library\Billing\Billing\Eway\EwayInternal.cs:line 552
at Billing.Eway.Eway.BillAllUsers() in c:\Users\Ian\Source\Repos\billing-class-library\Billing\Billing\Eway\Eway.cs:line 138
And my code for Billing.Eway.EwayInternal.SuccessCustomerRenewal:
internal static void SuccessCustomerRenewal(string username, Bill bill, EwayTransaction transaction)
{
// Give them their points!
ApplyBillToCustomerAccount(username, bill, true);
BillingEmail.SendRenewalSuccessEmail(username, bill, transaction);
using (MsSqlDataClassesDataContext msSqlDb = new MsSqlDataClassesDataContext())
{
// TODO: Remove this logging
msSqlDb.Log = new StreamWriter(#"logs\db\" + Common.GetCurrentTimeStamp() + "-MsSQL.txt", true) { AutoFlush = true };
EwayCustomer ewayCustomer = msSqlDb.EwayCustomers.First(c => c.Username == username);
ewayCustomer.NextBillingDate = Common.GetPlanExpiry(bill.BillPlan);
using (MySqlDataContext mySqlDb = new MySqlDataContext())
{
// TODO: Remove this logging
mySqlDb.Log = new StreamWriter(#"logs\db\" + Common.GetCurrentTimeStamp() + "-MySQL.txt", true) { AutoFlush = true };
BillingMySqlContext.Customer grasCustomer = mySqlDb.Customers.First(c => c.Username == username);
// Extend their membership date out so that the plan doesn't expire because of a failed credit card charge.
grasCustomer.MembershipDate =
ewayCustomer.NextBillingDate.AddDays(1);
mySqlDb.SubmitChanges(); // <-- This is line 552
}
msSqlDb.SubmitChanges();
}
}
I know that the issue occurs on the mySqlDb.SubmitChanges() line, since that DB context is the one using Devart (Linq solution for MySQL databases): the other context uses pure MS Linq.
Not only is the change written to the MySql DB (inner using block), but it is also written to the MsSql DB (outer using block). But that's where the magical success ends.
If I could I would write a Minimal, Complete and Verifiable example, but strangely I'm unable to generate a Devart ChangeConflictException.
So, why does the change get saved to the database after a Devart.Data.Linq.ChangeConflictException? When I previously encountered System.Data.Linq.ChangeConflictException changes weren't saved.
Edit 1:
I've also now included the .PDB file and gotten line number confirmation of the exact source of the exception.
Edit 2:
I now understand why I can't generate a ChangeConflictException, so how is it happening here?
These are the attributes for MembershipDate:_
[Column(Name = #"Membership_Date", Storage = "_MembershipDate", CanBeNull = false, DbType = "DATETIME NOT NULL", UpdateCheck = UpdateCheck.Never)]
I know I can explicitly force my changes through to override any potential conflict, but that seems undesirable (I don't know what I would be overriding!). Similarly I could wrap the submit in a try block, and retry (re-reading each time) until success, but that seems clunky. How should I deal with this intermittent issue?
Edit 3:
It's not caused by multiple calls. This function is called in one place, by a single-instance app. It creates log entries every time it is run, and they are only getting created once. I have since moved the email call to the top of the method: the email only gets sent once, the exception occurs, and database changes are still made.
I believe it has something to do with the using blocks. Whilst stepping through the debugger on an unrelated issue, I entered the using block, but stopped execution before the SubmitChanges() call. And the changes were still written to the database. My understanding was that using blocks were to ensure resources were cleaned up (connections closed, etc), but it seems that the entire block is being executed. A new avenue to research...
But it still doesn't answer how a ChangeConflictException is even possible given Devart explicitly ignores them.
Edit 4:
So I wasn't going crazy, the database change did get submitted even after I ended execution in the middle of the using block, but it only works for websites.
Edit 5:
As per #Evk's suggestion I've included some DB logging (and updated the stacktrace and code snippet above). The incidence rate of this exception seems to have dropped, as it has only just happened since I implemented the logging. Here are the additional details:
Outer (MS SQL) logfile:
SELECT TOP (1) [t0].[id], [t0].[Username], [t0].[TokenId], [t0].[PlanId], [t0].[SignupDate], [t0].[NextBillingDate], [t0].[PaymentType], [t0].[RetryCount], [t0].[AccountStatus], [t0].[CancelDate]
FROM [dbo].[EwayCustomer] AS [t0]
WHERE [t0].[Username] = #p0
-- #p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [dyonis]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.18408a
(It just shows the SELECT call (.First()), none of the updates show).
Inner (MySQL) logfile:
SELECT t1.Customer_ID, t1.Username, t1.Account_Group, t1.Account_Password, t1.First_Name, t1.Last_Name, t1.Account_Type, t1.Points, t1.PromoPoints, t1.Phone, t1.Cell, t1.Email, t1.Address1, t1.Address2, t1.City, t1.State, t1.Country, t1.Postcode, t1.Membership_Group, t1.Suspend_On_Zero_Points, t1.Yahoo_ID, t1.MSN_ID, t1.Skype_ID, t1.Repurchase_Thresh, t1.Active, t1.Delete_Account, t1.Last_Activity, t1.Membership_Expires_After_x_Days, t1.Membership_Date, t1.auth_name, t1.created_by, t1.created_on, t1.AccountGroup_Points_Used, t1.AccountGroup_Points_Threashold, t1.LegacyPoints, t1.Can_Make_Reservation, t1.Gallery_Access, t1.Blog_Access, t1.Private_FTP, t1.Photometrica, t1.Promo_Code, t1.Promo_Expire_DTime, t1.Gift_FirstName, t1.Gift_LastName, t1.Gift_Email, t1.Gift_Phone, t1.Gift_Active, t1.NoMarketingEmail, t1.Can_Schedule, t1.Refered_By, t1.Q1_Hear_About_Us, t1.Q2_Exp_Level, t1.Q3_Intrests, t1.GIS_DTime_UTC, t1.Membership_Expire_Notice_Sent, t1.Promo_Expire_Notice_Sent, t1.isEncrypted, t1.PlanId
FROM grasbill.customers t1
WHERE t1.Username = :p0 LIMIT 1
-- p0: Input VarChar (Size = 6; DbType = AnsiString) [dyonis]
-- Context: Devart.Data.MySql.Linq.Provider.MySqlDataProvider Mapping: AttributeMappingSource Build: 4.4.519.0
UPDATE grasbill.customers SET Membership_Date = :p1 WHERE Customer_ID = :key1
-- p1: Input DateTime (Size = 0; DbType = DateTime) [8/3/2016 4:42:53 AM]
-- key1: Input Int (Size = 0; DbType = Int32) [7731]
-- Context: Devart.Data.MySql.Linq.Provider.MySqlDataProvider Mapping: AttributeMappingSource Build: 4.4.519.0
(Shows the SELECT and UPDATE calls)
So the log files don't really give any clue as to what's happening, but again the MS SQL database has been updated! The NextBillingDate field has been set correctly, as per this line:
ewayCustomer.NextBillingDate = Common.GetPlanExpiry(bill.BillPlan);
If it hadn't been updated, the user would have been billed again on the next timer tick (5 mins later), and I can see from logging that didn't happen.
One other interesting thing to note is the log file timestamps. As you can see from the code above I grab the current (UTC) time for the log filename. Here is the information shown by Windows File Explorer:
The MS SQL logfile was created at 04:42 (UTC) and last modified at 14:42 (UTC+10, Windows local-time), but the MySQL logfile was last modified at 15:23 (UTC+10), 41 minutes after it was created. Now I assume the logfile StreamWriter is closed as soon as it leaves scope. Is this delay an expected side effect of the exception? Did it take 41 minutes for the garbage collector to realise I no longer needed a reference to the StreamWriter? Or is something else going on?
Well 6 months later I finally got to the bottom of this problem. Not sure if it will ever help anyone else, but I'll detail it anyway.
There were 2 problems in play here, and 1 of them was idiocy (as they usually are), but one was legitimately something I did not know or expect.
Problem 1
The reason the changes were magically made to the database even though there was an exception was because the very first line of code in that function ApplyBillToCustomerAccount(username, bill, true); updates the database! <facepalm>
Problem 2
The (Devart) ChangeConflictException isn't only thrown if the data has changed, but also if you're not making any changes. MS SQL stores DateTimes with great precision, but MySQL (or the one I'm running at least) only stores down to seconds. And here's where the intermittency came in. If my database calls were quick enough, or just near the second boundary, they both got rounded to the same time. Devart saw no changes to be written, and threw a ChangeConflictException.
I recently made some optimisations to the database which resulted in far greater responsiveness, and massively increased incidence of this exception. That was one of the clues.
Also I tried changing the Found Rows parameter to true as instructed in the linked Devart post but found it did not help in my case. Or perhaps I did it wrong. Either way now that I've found the source of the issue I can eliminate the duplicate database updates.

Is it possible to run console app on database change/update?

I have a database A which is connected to my website(ASP.NET MVC). Whenever there is a change/update in database A through the website, I want to run a console app to grab updated data from database A and pull it down to database B.
Is this possible to implement this function using SqlDependency or Service Broker, or is there a better way of doing it?
There is number of ways how you can do that. To name a few:
setup database mirroring
backup/restore whole db (can easily be overkill)
use custom scripts to update one db to another
use sync framework from ado.net
use some custom code to update second db
While you can setup first three to be completely on database level, 4,5 (and 3 as well) uses some application.
In order to call your code on time you can use both push and pull approaches, so either setup a timer or use SqlDependency to have a callback when update happened.
On database level you can setup trigger or have a recurring job setup.
You may implement SQL SERVER CLR integration in following ways:
Enable CLR with SQL server: https://msdn.microsoft.com/en-us/library/ms131048(SQL.100).aspx
Write CLR trigger : https://msdn.microsoft.com/en-us/library/ms131093(v=sql.100).aspx
For more info :https://msdn.microsoft.com/en-us/library/ms254498%28v=vs.110%29.aspx
UPDATE:
You may create a sp like bellow and call this sp in a trigger for that table: CREDIT :
CREATE PROCEDURE dbo.usp_ExecCmdShellProcess
AS
BEGIN
DECLARE #job NVARCHAR(100) ;
SET #job = 'xp_cmdshell replacement - ' + CONVERT(NVARCHAR, GETDATE(), 121) ;
EXEC msdb..sp_add_job #job_name = #job,
#description = 'Automated job to execute command shell script',
#owner_login_name = 'sa', #delete_level = 1 ;
EXEC msdb..sp_add_jobstep #job_name = #job, #step_id = 1,
#step_name = 'Command Shell Execution', #subsystem = 'CMDEXEC',
#command = 'c:\Testconsole.exe', #on_success_action = 1 ;
EXEC msdb..sp_add_jobserver #job_name = #job ;
EXEC msdb..sp_start_job #job_name = #job ;
END ;
GO
and I can't say better ,but I think you can go for a trigger and call a clr function in the SQL server a https://msdn.microsoft.com/en-us/library/w2kae45k.aspx.

DROP command denied to user X for table 'Y'

We are getting ready for a big SQL migration.
Currently, I have the code written, and I am testing it out with data on my local machine.
Step 1 is to throw out the existing data in the table before I import the new stuff:
using (var txn = m_mySqlConnection.BeginTransaction()) {
using (var cmd = new MySqlCommand("TRUNCATE TABLE `blah_blah`;", m_mySqlConnection, txn)) {
cmd.ExecuteNonQuery();
}
// other code
}
But, the TRUNCATE command is throwing an exception whenever I try to execute it with the MySQL user account I am running the code with:
I tried going into MySQL Workbench to give this userid DROP permission, but all I could find was a way to add DROP under the View section.
I tried that, but it did not work.
How do I go about giving this user the ability to remove the data in these tables so that I can test my populate script?
TRUNCATE deletes the table. Try using DELETE FROM Table.

Categories