How can I handle oracle exceptions using Enterprise Library in C#? - c#

I'm building an ASP.NET web application using C#, Enterprise Library and Oracle for a database. This is the first time I'm using Oracle as the database and so far my experience hasn't been all that great (I prefer using MS SQL Server). So here's the scenario:
The users have the ability to enter and change the value of a unique field in the database (e.g.- username). If the user enters a value (in this context, a username) which has been already entered as a different record then the requirement is to the inform the user as such. Since the inserting and/or updating is done via stored procedure I decided to handle the exception within the store procedure. I did so by declaring an out parameter called erCode of type NUMBER and added the following the block to the stored procedure after the SQL statement.
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
erCode := SQLCODE
In the event the INSERT/UPDATE operation was successful I return erCode with value 0.
Naturally since there was a return value an exception was never caught in the try-catch block of the code in the data access layer (DAL) of the application. Therefore I returned the value of erCode from DAL to the business logic layer (BLL) and then to the UI layer where I handled it inside the try block by:
if (errorCode = -1)
{
lblMsg.Text = "Username already exists";
}
else
{
lblMsg.Text = "Username changed successfully";
}
I realise this is a horrible way to do this even if the oracle error code is a value apart from "-1", then the Username changed successfully will be shown which would be completely misleading.
I'm not supposed to use the System.Data.OracleClient.OracleException class since its not looked upon favourably when we use that class in adjacent with Enterprise Library at my work place. If we are using Enterprise Library, then we should use that alone for all database related functions.
This bring me to my question; how can I handle such a scenario? Is it possible to do it using Enterprise Library alone?

If you need to take the code as what you are doing and still be on the safe side, then you can do like this:
When no error is encountered, then if I assume that ErrorCode
returned will be a null value, you may do this:
if(erCode==null)
errorCode = 1;
Setting ErrorCode =1 will certainly tell that operation was
success.
Modify your code as:
if (errorCode = -1)
{
lblMsg.Text = "Username already exists";
}
else if(errorCode = 1)
{
lblMsg.Text = "Username changed successfully";
}
else{
lblMsg.Text = "Unknown error while updating!";
}
So if there will be any other error code, relevant message is
shown!
Hope you find it worthy!

Related

SQL Server Reporting Services Cannot Set Data Source Connection String

We have a number of SSRS sites serving reports to various locations. Each of these servers all have custom connections in each and every report (don't ask why, that's a tale too torrid to tell). Our goal is to replace all of these custom data sources with a single shared data source for all reports on each server.
To that end, I have created a C# program that will find each report on each server and point the current custom data sources to a currently existing shared data source. This executes and seems to work fine.
My next goal is to use C# to create the shared data source on each server where none currently exists.
My current dilemma arises here:
private static void CreateSharedDataSource(string user, string password, string connection)
{
DataSourceDefinition source = new DataSourceDefinition();
source.CredentialRetrieval = CredentialRetrievalEnum.Store;
source.ConnectString = connection;
source.Enabled = true;
source.EnabledSpecified = true;
source.Extension = "SQL";
source.ImpersonateUser = false;
source.ImpersonateUserSpecified = false;
source.Prompt = "Enter a user name and password to access the data source:";
source.UserName = user;
source.Password = password;
source.WindowsCredentials = true;
source.OriginalConnectStringExpressionBased = true;
source.UseOriginalConnectString = true;
try
{
service.CreateDataSource("DbDataSource", "/DsFolder", false, source, null);
Console.WriteLine("Data source created successfully");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
The data source is created correctly and the user name and password are updated correctly. The problem is that, when I look at the newly created data source on the server, the Connect string property is empty and, yes, the correct value is being passed to it in the method above. If I plug that value into the shared source on the server and test the connection, it works fine, but I cannot get the C# program to update that value itself.
So, is there something subtle I'm missing? Am I misinterpreting a setting up there? Did I set a value wrong?
Clues appreciated.
I've never tried anything like this but a quick bit of research uncovered this which may be helpful.
https://learn.microsoft.com/en-us/dotnet/api/reportservice2010.datasourcedefinition.originalconnectstringexpressionbased?view=sqlserver-2016
It states
"Expression-based connection strings are not supported in shared data
sources."
Assuming the conneciton string is just a plain text string then I would guess that you could set this to false. This may help preserve the string you pass in.
Alright, found my answer. I had a pre-existing data source that was working so, instead of creating it from scratch, I copied that and only changed the name. That created a data source where the Connect string did persist. Comparing the settings in that with what I was setting revealed:
source.UseOriginalConnectString = false;
whereas, my code was:
source.UseOriginalConnectString = true;
Looking that up in docs and it tells me "If true, the value of the ConnectString property is ignored."
Hmmm... that's intuitive. That's not what that sounds like at all. :)

Best practice to retrieve error message from database

We have list of error messages in our database table and we fetch these error messages from table when we face some business validation error.
for e.g. If in c# code we find that the calculated risk % is more then allowed value, when use below code
string sError = GetErrorText(6610); // get error message from application cache
DisplayErrorPopup(sError ); // load a popup to display error to user.
Now we have found that there are scenarios where we have to validate stuff few rules from stored Procedure. for e.g. "No active supervisor for worker."
My question is how should we handle this scenario when validation happens in database?
A. Should we return error text "No active supervisor for worker." as out param of SP and pass it to DisplayErrorPopup
OR
B. Return the error id (which is present in table) and then use GetErrorText(834) and then pass the text to DisplayErrorPopup;
My concerns are
1. Is there any industry standard to best practice to handle error messages and texts.
2. Is there any drawback of returning string / varchar from database when we have option of returning number.
Please guide me on this.
I would suggest throwing (raising) a custom error in SQL (you could select the text out of a table if you like) and letting your application catch that error. This will allow you to let your application decide how to handle different errors based on how critical they are.
using (SqlConnection conn = new SqlConnection(connection))
{
SqlCommand sqlCommand = new SqlCommand(query, conn);
sqlCommand.CommandTimeout = timeout;
sqlCommand.CommandType = CommandType.Text;
conn.Open();
object result = sqlCommand.ExecuteScalar();
return result;
}
Running the above inside of a try catch block will allow you to handle your errors more elegantly inside of your C# app
Both your options are equally correct.You can either return an error text from database, or return some error code and display error message based on that code in your application.
Also, try this atricle from codeproject as it has both industry standard and best practice to handle error messages and texts explained in detail.

VS 2013 bombs out when MVC steps in EF6 call using SQL2014

I have an MVC5 project that is divided into a main XXXX.Site and a XXXX.Data dll that has EF6 connecting to the MS-SQL 2014 database.
When I'm on the MVC controller and press F10 at the call that runs inside the XXXX.Data dll, all goes well. If I go inside the DLL code and put a breakpoint on the actual EF call ...Visual Studio simply bombs out.
I tried different things like re-adding EF6.1.1.1, I on both DLL and MVC site but nothing works. I tried removing and then adding completely EF. I tried a new project and just put the code to run a simple stored proc, I even tried to combine the Data.dll into the MVC Site code moving all DB access to the MVC Site (mainly deleted the DLL itself) ...but nothing worked !!
This is what I noticed so far:
1) if I put a breakpoint on the code I wrote to call this stored procedure ...Visual Studio simply bombs out.
try
{
using (MyDB db = new MyDB())
{
// IF BREAKPOINT IS ON LINE BELOW, EXECUTION STOPS ABRUPTLY
db.MyStoredProc(value1, value2);
}
}
catch (Exception ex)
{
string s = ex.Message;
return false;
}
No exception is raised, and when it occurs the browsers kind of flashes a couple of times. Then this message appears on the Output window:
A first chance exception of type 'System.AccessViolationException' occurred in XXXX.Data.dll
2) If instead I put the breakpoint inside the actual auto generated EF6 code, the program does runs normally.
public virtual int MyStoredProc(string value1, string value2)
{
var value1Parameter = value1 != null ?
new ObjectParameter("Value1", value1) :
new ObjectParameter("Value1", typeof(string));
var value2Parameter = value2 != null ?
new ObjectParameter("Value2", value2) :
new ObjectParameter("Value2", typeof(string));
// IF BREAKPOINT IS ON LINE BELOW, EXECUTION RUNS NORMALLY
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("MyStoredProc", value1Parameter, value2Parameter);
}
Note that I have both EntityFramework and EntitySQLServer dlls on both bin folders (MVC and DLL).
Questions:
Is it an issue with SQL2014? I don't have this happening when connecting to SQL2012.
Is there a setting that will display the actual exception occurred?
Why is VS bombing out instead of displaying the actual error?
I was getting this "Attempted to read or write protected memory exception" error while using a SQL Server stored procedure that had an output parameter of type 'Date'. I tried various things without success and, in the interest of time, settled on the following solution.
1) Remove the output parameter of type date from the stored procedure.
2) Return a string via a select statement in the stored procedure instead.
SELECT CONVERT(char(10), #AsOfDate, 20) AS AsOfDate
3) Convert the string returned from the stored procedure to a DateTime value in C#.
DateTime asOfDate = DateTime.Now;
using (var context = new DA.MyEntities())
{
var procResult = context.myStoredProcedure(myParameter).FirstOrDefault();
DateTime.TryParse(procResult, out asOfDate);
}
I'm not super happy with this compromise, but it did allow me to move forward.

Local ReportViewer Fails at Unnecessary Login to Database

I have inherited an application that runs small reports locally using Microsoft Web ReportViewer. Our application allows you to "Preview/Print" a report by clicking on a specific button that routes the user to a URL that allows them to download the report as a PDF. We have recently received the requirement to save these PDFs to the document table in our database. I have been able to get this to work successfully on localhost; however, when I publish the application to our IIS server, I get the following error:
System.Data.SqlClient.SqlException: Login failed for user 'Domain\Servername$'.
I've reviewed all of the sites that I could find involving this error (including this one) - most point to adding the server account to the SQL database; however, this shouldn't be an issue, since the button to preview/print the document is still functional and works as expected when the application is published and all of the data is held in a local object, which was previously pulled from the database (the model parameter below). The button and the auto-generation feature use the same two methods to create the PDF document(see below).
Here's some code:
public static byte[] CreatePDFDocument(DocumentTemplateType template, Request model)
{
Warning[] warnings;
string[] streamIds;
string mimeType = string.Empty;
string encoding = string.Empty;
string extension = string.Empty;
ReportViewer viewer = new ReportViewer();
viewer.ProcessingMode = ProcessingMode.Local;
viewer.LocalReport.ReportEmbeddedResource = "Xxx.Xxx.Bll.ReportViewerRDLCs." + template.RdlcFilename;
switch ((DocumentType)template.DocumentTypeId)
{
case eDocumentType.Report1:
viewer.LocalReport.SetParameters(GetForm1Parameters(model));
break;
/**
* Several other reports are in this switch. All reports have the
* same issue - all but one are removed for brevity.
*/
}
byte[] bytes = viewer.LocalReport.Render("PDF", null, out mimeType, out encoding, out extension, out streamIds, out warnings);
return bytes;
//return new byte[5] {5,6,7,8,9}; - used for troubleshooting.
}
public static List<ReportParameter> GetReport1Parameters(Request model)
{
List<ReportParameter> rptParams = new List<ReportParameter>();
//Start comment
rptParams.Add(new ReportParameter("EmployeeFullName", string.Format("{0:NN}", model.Employee)));
rptParams.Add(new ReportParameter("EmployeePhoneNumber", string.Format("{0:(###) ###-####}", Convert.ToInt64(model.Employee.PhoneNumber))));
rptParams.Add(new ReportParameter("HrchyShortDesc", model.Employee.HrchyShortDesc));
rptParams.Add(new ReportParameter("RequestDate", model.RequestDate.ToShortDateString()));
rptParams.Add(new ReportParameter("RequestRequested", model.RequestRequestType));
rptParams.Add(new ReportParameter("ReasonForRequest", model.RequestRequestReason));
rptParams.Add(new ReportParameter("LogNumber", model.CaseId));
if (!string.IsNullOrWhiteSpace(model.TimeSensitiveReason)) rptParams.Add(new ReportParameter("TimeSensitiveReason", model.TimeSensitiveReason));
var lastAction = model.LastActionOfType(WorkflowStateActionType.EmployeeConfirmation);
if (lastAction != null)
{
rptParams.Add(new ReportParameter("TodaysDate", lastAction.ActionDate.ToShortDateString()));
rptParams.Add(new ReportParameter("EmpConfirmed", "true"));
}
else rptParams.Add(new ReportParameter("TodaysDate", DateTime.Now.ToShortDateString()));
//end comment
return rptParams;
}
Through a lot of commenting in and out and pushes to our server, I've deduced the following:
From what I can tell, the error occurs on calling GetReport1Parameters. In the code above, I included a start and end comment - I've commented out everything in between, leaving only the list initialization and return statement (of an empty list) and still received the error.
I've commented out the call to GetReport1Parameters and returned a nonsensical byte array and didn't receive an Exception.
All functionality works fine on localhost and when I step through the functions, all of the variables seem to appear normal.
Things I've tried to do to remedy the situation:
1. Removed connection strings from the app.config, so that the application has to go to the web.config to get the correct strings (even though they were the same).
2. Commented in and out different sections of code to determine the problem area.
3. Tried calling the GetReport1Parameters method and returning null, leading to a null reference exception.
4. Tried calling the GetReport1Parameters with an empty parameter list, leading to the error mentioned above.
5. Tried running the report with no parameters (not even a blank list), got a ReportProcessingException for missing params.
Some additional information:
We use a service account for the application using impersonate identity in the web.config. That line is commented out on localhost, but is running on IIS.
All of other database interaction works correctly.
All of our database interaction is done using LINQ to SQL - model is an object based off of a database table, with some additional information that is calculated dynamically.
My desired outcome is that both the autogenerated documents and the preview/print documents both work. I have a feeling that this may be something simple that I'm overlooking, but I've already spent several hours today trying to fix this.
I can't think of any other pertinent information, but if you have questions I'll be more than happy to answer them.
Edit: Additional attempts to find solution:
Tried setting LINQ Deferred Loading equal to false. This caused more problems than it solved.
Implemented IReportServerCredentials and assigned the ReportViewer's ServerReport.ReportServerCredentials with the correct database credentials.
Assigned all pertinent report parameters to a Dictionary, and then called .ToString() on every object to ensure that it is pulled from the database. Then assigned those strings from the dictionary to the report parameters, so that ReportViewer should be receiving the data from the string pool, as opposed to pulling it from the database.
Even though you are using an ObjectDataSource to pass data to your report, Report Viewer will still invoke the Select method, which in turn could cause database access to occur. So even though it may seem that the login is unnecessary, you would need to dig into the data access methods you supplied with your ObjectDataSource to know for sure.
The error you are getting is being caused by a bug in Report Viewer 2010 that is describe in the following Microsoft Connect article:
ReportViewer.LocalReport.Render and ReportViewer.LocalReport.SetParameters changes ImpersonationLevel to None
Although the article mentions this problem should be fixed in Service Pack 1, it does not appear to be the case. I have not verified if this problem is fixed in Report Viewer 2012.
I worked around the problem by changing my data access layer to compare the current identity against the one in my HttpContext and restore it if necessary using the following code snippet:
System.Security.Principal.IIdentity id = System.Web.HttpContext.Current.User.Identity
if (id.Name != System.Security.Principal.WindowsIdentity.GetCurrent().Name)
{
context = (id as System.Security.Principal.WindowsIdentity).Impersonate()
}
I do this right before I connect to the database and undo it as soon as the connection is open.
I am not exactly thrilled with this workaround, mainly because now my data access layer is referencing the UI layer (System.Web).

Linq to SQL Row Not Found or Changed Exception on Insert Operation Ajax Postback in MVC3

I have an MVC3 View that's posting back a set of input values via Ajax to my controller. My controller is then creating a new FieldTripRoute object on my context and attempting to insert it into the database.
I just can't figure out what's going on. I've triple checked my Designer schema and my DB schema and they match perfectly. So it can't be the normal issue of a column not existing or being nullable in one area but not another. However, I keep receiving a "Row Not Found or Changed" exception every time I attempt to submit changes.
The stack trace on the exception looks like this:
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges()
at ManageMAT.Controllers.FieldTripController.RouteAdd(Int32 id, FormCollection collection)
This is the code that's being called to add the new Route object from the Controller:
[HttpPost]
public ActionResult RouteAdd(int id, FormCollection collection)
{
FieldTrip trip = context.FieldTrips.Single(ft => ft.ID == id);
if (trip == null) return Json(new { success = false, message = "Field trip not found." }); ;
try
{
FieldTripRoute tripRoute = new FieldTripRoute();
tripRoute.FieldTripID = trip.ID;
tripRoute.Date = DateTime.Parse(collection["Date"]);
tripRoute.ArrivalTime = DateTime.Parse(collection["ArrivalTime"] + " " + DateTime.Now.ToShortDateString());
tripRoute.DepartureTime = DateTime.Parse(collection["DepartureTime"] + " " + DateTime.Now.ToShortDateString());
tripRoute.Destination = collection["Destination"];
tripRoute.PickupLocation = collection["PickupLocation"];
tripRoute.RouteID = Convert.ToInt32(collection["RouteID"]);
context.FieldTripRoutes.InsertOnSubmit(tripRoute);
context.SubmitChanges();
return Json(new { success = true, message = "Success!" });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
And here is my Designer and DB Table Columns:
I've also attempted to view the SQL this is outputting in both the logging available on the context object and in SQL Profiler, but it seems to be failing before it's even hitting the Database server.
Edit: Forgot to add one other thing, when I'm initially creating the new FieldTripRoute object at the beginning of the Add action I noticed that it's not retrieving the correct ID from the database identity series. Perhaps this is related?
I've also tried setting the Update Check on every field in the designer to Never just to see if it was some kind of bizarre concurrency collision going on, but I am still receiving the same error.
I'm really at a loss for what could be causing this issue. Any ideas are appreciated.
This message is thrown every time the row is not inserted for whatever reason. For DML statements Linq to SQL checks the number of modified rows. SQL Server returns this count. It is checked to be one.
The big question is why is the count zero and yet no error message is being sent by SQL Server. Start SQL Server profiler and post the SQL that L2S generates. Run the SQL manually and see what happens. Does a row get inserted? Does its identity value get returned?
Edit: More debugging ahead: Shut down SQL server just before you to the SubmitChanges to make sure that the database is not being hit. Lets make sure to cut this branch off the search tree.
Next, step into the Linq to SQL source code to see whats up. If you have R# this is easy: Press ctrl-shift-t, search ChangeProcessor, click and navigate to the "sources from symbol files". Find the function SubmitChanges and put a breakpoint in there. If you don't have R#, you need to dig out some tutorial on the web for this (it's going to take about 5min).
Step through the source to find why the exception is thrown.

Categories