I'm working on a C# program that uses a SQL Server Compact database. I have a query where I want to select the highest number in a specific field that looks like this:
SELECT MAX(nr2) FROM TABLE WHERE nr1 = '10'
This works as inteneded when there is a row where nr1 is 10. But I would expect to not get an answer when that row doesn't exist, but instead I get an empty field. So in my C# code I have:
text = result[0].ToString();
When I get a value from my SQL query the string contains a number and when the specified row doesn't exist I get an empty string.
This isn't really a big problem but I would be able to do the following check:
if (result.Count > 0)
Instead of:
if (result[0].ToString() == "")
which I have to do at the moment since count is always larger than 0.
Talk about using a sledgehammer to crack a nut, but...
I don't test it with C# code, but in SQL Server Management Studio, if you run...
SELECT MAX(nr2) FROM TABLE WHERE nr1 = '10' HAVING MAX(nr2) IS NOT NULL
, the result is an empty collection, not a collection with one null (or empty) element.
NOTE: My answer is based on this SO Answer. It seems that MAX and COUNT SQL functions returns always a single row collection.
That SQL statement will always return a result... if the base query returns no result then the value of max() is null !
if you are using ADO.NEt, you could use ExecuteScalar, here an example :
private int GetIDNum()
{
SqlConnection connection = new SqlConnection("connectionstring");
using(SqlCommand command = new SqlCommand("SELECT MAX(nr2) FROM TABLE WHERE nr1 = '10'", connection))
{
try
{
connection.Open();
object result = command.ExecuteScalar();
if( result != null && result != DBNull.Value )
{
return Convert.ToInt32( result );
}
else
{
return 0;
}
}
finally
{
connection.Close();
}
}
}
Related
I have a C# code which does lot of insert statements in a batch. While executing these statements, I got "String or binary data would be truncated" error and transaction roledback.
To find out the which insert statement caused this, I need to insert one by one in the SQLServer until I hit the error.
Is there clever way to findout which statement and which field caused this issue using exception handling? (SqlException)
In general, there isn't a way to determine which particular statement caused the error. If you're running several, you could watch profiler and look at the last completed statement and see what the statement after that might be, though I have no idea if that approach is feasible for you.
In any event, one of your parameter variables (and the data inside it) is too large for the field it's trying to store data in. Check your parameter sizes against column sizes and the field(s) in question should be evident pretty quickly.
This type of error occurs when the datatype of the SQL Server column has a length which is less than the length of the data entered into the entry form.
this type of error generally occurs when you have to put characters or values more than that you have specified in Database table like in that case: you specify
transaction_status varchar(10)
but you actually trying to store
_transaction_status
which contain 19 characters. that's why you faced this type of error in this code
Generally it is that you are inserting a value that is greater than the maximum allowed value. Ex, data column can only hold up to 200 characters, but you are inserting 201-character string
BEGIN TRY
INSERT INTO YourTable (col1, col2) VALUES (#val1, #val2)
END TRY
BEGIN CATCH
--print or insert into error log or return param or etc...
PRINT '#val1='+ISNULL(CONVERT(varchar,#val1),'')
PRINT '#val2='+ISNULL(CONVERT(varchar,#val2),'')
END CATCH
For SQL 2016 SP2 or higher follow this link
For older versions of SQL do this:
Get the query that is causing the problems (you can also use SQL Profiler if you dont have the source)
Remove all WHERE clauses and other unimportant parts until you are basically just left with the SELECT and FROM parts
Add WHERE 0 = 1 (this will select only table structure)
Add INTO [MyTempTable] just before the FROM clause
You should end up with something like
SELECT
Col1, Col2, ..., [ColN]
INTO [MyTempTable]
FROM
[Tables etc.]
WHERE 0 = 1
This will create a table called MyTempTable in your DB that you can compare to your target table structure i.e. you can compare the columns on both tables to see where they differ. It is a bit of a workaround but it is the quickest method I have found.
It depends on how you are making the Insert Calls. All as one call, or as individual calls within a transaction? If individual calls, then yes (as you iterate through the calls, catch the one that fails). If one large call, then no. SQL is processing the whole statement, so it's out of the hands of the code.
I have created a simple way of finding offending fields by:
Getting the column width of all the columns of a table where we're trying to make this insert/ update. (I'm getting this info directly from the database.)
Comparing the column widths to the width of the values we're trying to insert/ update.
Assumptions/ Limitations:
The column names of the table in the database match with the C# entity fields. For eg: If you have a column like this in database:
You need to have your Entity with the same column name:
public class SomeTable
{
// Other fields
public string SourceData { get; set; }
}
You're inserting/ updating 1 entity at a time. It'll be clearer in the demo code below. (If you're doing bulk inserts/ updates, you might want to either modify it or use some other solution.)
Step 1:
Get the column width of all the columns directly from the database:
// For this, I took help from Microsoft docs website:
// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.getschema?view=netframework-4.7.2#System_Data_SqlClient_SqlConnection_GetSchema_System_String_System_String___
private static Dictionary<string, int> GetColumnSizesOfTableFromDatabase(string tableName, string connectionString)
{
var columnSizes = new Dictionary<string, int>();
using (var connection = new SqlConnection(connectionString))
{
// Connect to the database then retrieve the schema information.
connection.Open();
// You can specify the Catalog, Schema, Table Name, Column Name to get the specified column(s).
// You can use four restrictions for Column, so you should create a 4 members array.
String[] columnRestrictions = new String[4];
// For the array, 0-member represents Catalog; 1-member represents Schema;
// 2-member represents Table Name; 3-member represents Column Name.
// Now we specify the Table_Name and Column_Name of the columns what we want to get schema information.
columnRestrictions[2] = tableName;
DataTable allColumnsSchemaTable = connection.GetSchema("Columns", columnRestrictions);
foreach (DataRow row in allColumnsSchemaTable.Rows)
{
var columnName = row.Field<string>("COLUMN_NAME");
//var dataType = row.Field<string>("DATA_TYPE");
var characterMaxLength = row.Field<int?>("CHARACTER_MAXIMUM_LENGTH");
// I'm only capturing columns whose Datatype is "varchar" or "char", i.e. their CHARACTER_MAXIMUM_LENGTH won't be null.
if(characterMaxLength != null)
{
columnSizes.Add(columnName, characterMaxLength.Value);
}
}
connection.Close();
}
return columnSizes;
}
Step 2:
Compare the column widths with the width of the values we're trying to insert/ update:
public static Dictionary<string, string> FindLongBinaryOrStringFields<T>(T entity, string connectionString)
{
var tableName = typeof(T).Name;
Dictionary<string, string> longFields = new Dictionary<string, string>();
var objectProperties = GetProperties(entity);
//var fieldNames = objectProperties.Select(p => p.Name).ToList();
var actualDatabaseColumnSizes = GetColumnSizesOfTableFromDatabase(tableName, connectionString);
foreach (var dbColumn in actualDatabaseColumnSizes)
{
var maxLengthOfThisColumn = dbColumn.Value;
var currentValueOfThisField = objectProperties.Where(f => f.Name == dbColumn.Key).First()?.GetValue(entity, null)?.ToString();
if (!string.IsNullOrEmpty(currentValueOfThisField) && currentValueOfThisField.Length > maxLengthOfThisColumn)
{
longFields.Add(dbColumn.Key, $"'{dbColumn.Key}' column cannot take the value of '{currentValueOfThisField}' because the max length it can take is {maxLengthOfThisColumn}.");
}
}
return longFields;
}
public static List<PropertyInfo> GetProperties<T>(T entity)
{
//The DeclaredOnly flag makes sure you only get properties of the object, not from the classes it derives from.
var properties = entity.GetType()
.GetProperties(System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.DeclaredOnly)
.ToList();
return properties;
}
Demo:
Let's say we're trying to insert someTableEntity of SomeTable class that is modeled in our app like so:
public class SomeTable
{
[Key]
public long TicketID { get; set; }
public string SourceData { get; set; }
}
And it's inside our SomeDbContext like so:
public class SomeDbContext : DbContext
{
public DbSet<SomeTable> SomeTables { get; set; }
}
This table in Db has SourceData field as varchar(16) like so:
Now we'll try to insert value that is longer than 16 characters into this field and capture this information:
public void SaveSomeTableEntity()
{
var connectionString = "server=SERVER_NAME;database=DB_NAME;User ID=SOME_ID;Password=SOME_PASSWORD;Connection Timeout=200";
using (var context = new SomeDbContext(connectionString))
{
var someTableEntity = new SomeTable()
{
SourceData = "Blah-Blah-Blah-Blah-Blah-Blah"
};
context.SomeTables.Add(someTableEntity);
try
{
context.SaveChanges();
}
catch (Exception ex)
{
if (ex.GetBaseException().Message == "String or binary data would be truncated.\r\nThe statement has been terminated.")
{
var badFieldsReport = "";
List<string> badFields = new List<string>();
// YOU GOT YOUR FIELDS RIGHT HERE:
var longFields = FindLongBinaryOrStringFields(someTableEntity, connectionString);
foreach (var longField in longFields)
{
badFields.Add(longField.Key);
badFieldsReport += longField.Value + "\n";
}
}
else
throw;
}
}
}
The badFieldsReport will have this value:
'SourceData' column cannot take the value of
'Blah-Blah-Blah-Blah-Blah-Blah' because the max length it can take is
16.
It could also be because you're trying to put in a null value back into the database. So one of your transactions could have nulls in them.
Most of the answers here are to do the obvious check, that the length of the column as defined in the database isn't smaller than the data you are trying to pass into it.
Several times I have been bitten by going to SQL Management Studio, doing a quick:
sp_help 'mytable'
and be confused for a few minutes until I realize the column in question is an nvarchar, which means the length reported by sp_help is really double the real length supported because it's a double byte (unicode) datatype.
i.e. if sp_help reports nvarchar Length 40, you can store 20 characters max.
Checkout this gist.
https://gist.github.com/mrameezraja/9f15ad624e2cba8ac24066cdf271453b.
public Dictionary<string, string> GetEvilFields(string tableName, object instance)
{
Dictionary<string, string> result = new Dictionary<string, string>();
var tableType = this.Model.GetEntityTypes().First(c => c.GetTableName().Contains(tableName));
if (tableType != null)
{
int i = 0;
foreach (var property in tableType.GetProperties())
{
var maxlength = property.GetMaxLength();
var prop = instance.GetType().GetProperties().FirstOrDefault(_ => _.Name == property.Name);
if (prop != null)
{
var length = prop.GetValue(instance)?.ToString()?.Length;
if (length > maxlength)
{
result.Add($"{i}.Evil.Property", prop.Name);
result.Add($"{i}.Evil.Value", prop.GetValue(instance)?.ToString());
result.Add($"{i}.Evil.Value.Length", length?.ToString());
result.Add($"{i}.Evil.Db.MaxLength", maxlength?.ToString());
i++;
}
}
}
}
return result;
}
With Linq To SQL I debugged by logging the context, eg. Context.Log = Console.Out
Then scanned the SQL to check for any obvious errors, there were two:
-- #p46: Input Char (Size = -1; Prec = 0; Scale = 0) [some long text value1]
-- #p8: Input Char (Size = -1; Prec = 0; Scale = 0) [some long text value2]
the last one I found by scanning the table schema against the values, the field was nvarchar(20) but the value was 22 chars
-- #p41: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [1234567890123456789012]
In our own case I increase the sql table allowable character or field size which is less than the total characters posted from theĀ front end. Hence that resolve the issue.
Simply Used this:
MessageBox.Show(cmd4.CommandText.ToString());
in c#.net and this will show you main query , Copy it and run in database .
I'm having trouble with detecting when my value in C# code is null or not, (I'm using LINQ to call stored procedures from SQL SERVER), I mean I have issues when I want to determine did SQL returned something to me after I called SP which expecting one parameter (ArticleID) or acctualy SQL did not return anything ( null - row does not exist ).
In fact, I want to check does any of my articles has some "Sub articles", and I'm passing ArticleID from C# code to stored procedure.
So In case if article is there, I want to do something with it, in case article with passed ArticleID does not exist I want to do something also, so as I said I need to detect does my article with given ArticleID exist in my sql database.
Here is some of my code:
private void btnCheckForArticle(object sender, RoutedEventArgs e)
{
try
{
if (gridArticles.SelectedItems.Count > 0) {
Article a = (Article)gridArticles.SelectedItem;
if (a.ArticleID != null)
{
var existingArticle = DataServices.POS.proc_GetSubArticleByArticleID(a.ArticleID);
if (existingArticle != null)
{
//DO SOMETHING
return;
}
}
}
}
}
My stored procedure :
ALTER PROCEDURE [dbo].[proc_GetSubArticleByArticleID]
(
#ArticleID int
)
AS
BEGIN
Select *
From SubArticles as S
Where S.ArticleID = #ArticleID
END
It's interesting that I do not know how to detect in c# code is my value null or not, because obliviosuly even if there is no row in sql database still I'm not getting null, and because of that code below existingArticle != null will allways execute..
Now I will post what is happening with article which for sure does not have any subarticles, I mean, where result should be null 100%!
ID that I passed to procedure is 2351, so I executed sp which should return value directly on sql and I replaced #ArticleID with 2351 and ofcourse It did not return me any results, but how can I notice that in my C# code..
because existingArticle will never be null cuz somehow it allways has some value and code below my if (existingArticle != null) will allways execute. what I really dont want :/ ...
Thanks guys!
Cheers
Why don't you call FirstOrDefault()
var existingArticle = DataServices.POS.proc_GetSubArticleByArticleID(a.ArticleID).FirstOrDefault();
if (existingArticle != null)
{
//
return;
}
There is a special Datatype for checking null values from a database.
DBNull.Value
In your case:
if (existingArticle != DBNull.Value)
{
//DO SOMETHING
return;
}
First, modify your SP to have after BEGIN:
SET NOCOUNT ON
This is to avoid interference of "xx rows affected" message.
Seccond, dont do SELECT *, instead use
SELECT 1 From SubArticles as S
Where S.ArticleID = #ArticleID
Last, check for System.DBNull.Value instead of NULL, as this is what will be returned from DDBB. This is because in .Net value types as Boolean can't be null
I have a table adapter in Visual studio dataset. I aligned a value to the query inside the table adapter so I can get if the query returns 1 if there is data or 0 for null. example:
document.documentDataSetTableAdapters.Job_NoTableAdapter jnta =
new documentDataSetTableAdapters.Job_NoTableAdapter();
int result = jnta.FillBygetJob_No(documentDataSet1.Job_No);
It never happened to me that result get a value other than 0 or 1. here I got result = 2. so when I made the IF statement
if(result == 1){....}
else if(result == 0)
so I got the error. I solved the problem by doing this:
if(result == 0){...}
else{...}
So what I need is an explanation of how result got the value 2.
So you're filling a DataSet with this query(your comment):
SELECT JN_Full_Form, Job_No, Job_No_ID, year
FROM Job_No
WHERE (JN_Full_Form = (SELECT MAX(JN_Full_Form) AS Expr1
FROM Job_No AS Job_No_1))
Now you expect that it always returns none or a single record. But what if there are two or more records with the same JN_Full_Form which are equal to MAX(JN_Full_Form)?!
So it's absolutely fine to change th if:
if(result == 0)
{
//...
}
else
{
//...
}
Another option is to change the query itself to select one record at a max(presuming sql-server):
SELECT TOP 1 JN_Full_Form, Job_No, Job_No_ID, year
FROM Job_No
WHERE (JN_Full_Form = (SELECT MAX(JN_Full_Form) AS Expr1
FROM Job_No AS Job_No_1))
How can I INSERT values into SQL Server that are stored in a string[] such that some of the values should be disregarded in favor of the values stored in SQL Server as default constraints on the table? What do I need to pass(e.g. NULL or something else) to use those defaults?
Currently, I add all the defaults in my code, and it is getting bulky.
Below is my code:
if (value == "") value = "0";
string insert = "INSERT INTO " + table + " (" + columns + ") VALUES (" + atColumns + ")";
using (SqlConnection connect = new SqlConnection(connection))
{
using (SqlCommand command = new SqlCommand(insert, connect))
{
//adds values to corresponding parameters
for (int i = 0; i < table_cols.Length; i++)
{
command.Parameters.AddWithValue("#" + table_cols[i], table_vals[i]);
}
foreach (SqlParameter Parameter in command.Parameters)
{
if (Convert.ToString(Parameter.Value) == "")
{
Parameter.Value = DBNull.Value;
}
}
connect.Open();
command.ExecuteNonQuery();
response = "Success";
return response;
If a specific Parameter.Value is not-null and has a default set by SQL Server, this code will not work with null.
In SQL Server, this gets handled by omitting the value in your insert statement (this omission triggers inserting the default value for the table, whereas providing null produces errors).
If you want SQL Server to use the default value constraint for the column then don't include the column as part of the insert parameters.
Example:
--From SQL Server
CREATE TABLE Orders
(
Id INT IDENTITY PRIMARY KEY
,Amount INT NOT NULL
,Cost MONEY NOT NULL
,SaleDate DATE NOT NULL DEFAULT GETUTCDATE()
);
//From C#
public int Insert(decimal cost, int amount)
{
using (var connection = new SqlConnection(connectionString))
{
var command = connection.CreateCommand();
//Don't specify the SaleDate and it will insert the current time
command.CommandText = "INSERT INTO Orders(Amount, Cost) VALUES(#Amount, #Cost); SELECT SCOPE_IDENTITY();";
command.Parameters.AddWithValue("#Amount", amount);
command.Parameters.AddWithValue("#Cost", cost);
using(var reader = command.ExecuteReader())
{
if(reader.Read())
return Convert.ToInt32(reader[0]);
}
}
return 0;
}
If you want to use a parameters list in your C# code then just keep the parameter names grouped with their values and if the value is null and it has a default value then just skip it.
Passing in a NULL tells SQL that you want a NULL in that column overriding the default. If you want to pass something in pass in the keyword DEFAULT. I wrote an article, "keyword DEFAULT", about the usage:
The DEFAULT keyword causes the default value (from the constraint) to be inserted into the column.
Just remember that when you pass in DEFAULT don't put quotes around it. That makes it the string DEFAULT rather than the keyword DEFAULT.
The only other way of doing it I can think of would be triggers based your approach (and you're better off coding it at that point).
However, if you alter your approach to use stored procedures, you can do your value handling natively in SQL, otherwise you're stuck coding it into your app... might i recommend Ternary statements for your example: http://msdn.microsoft.com/en-us/library/ty67wk28%28v=vs.80%29.aspx .
If you include a column in the column list, it will try and insert the value you give, it. It will not assume that NULL means "just insert the default value".
If you don't want to insert a value into that column, don't include it in your column list (or value list, obviously).
While it may seem more efficient to loop through the table columns and be agnostic of the column name, type, etc. In the long run you may be better off handling each column explicitly so you can choose whether or not to use a default, verify the value, etc.
I actually used the INFORMATION_SCHEMA.COLUMNS table to pull back from SQL Server all of the Column Defaults. Then I just organized the defaults into a string[] and looped through it to insert defaults rather than nulls (some defaults are null).
Looking on some source code I've inherited, there's a snippet of code that calls a SQL stored procedure making an Update.
The stored procedure returns -1 in case something goes wrong:
IF ##Error <> 0
BEGIN
ROLLBACK TRAN
SELECT -1
RETURN
END
COMMIT TRAN
SELECT 0
The C# code is something like this:
System.Data.SqlClient.SqlDataReader myReader;
try{
SqlDbConnection.Open();
SqlDbCommand.Connection = SqlDbConnection;
SqlDbCommand.CommandType = System.Data.CommandType.StoredProcedure;
SqlDbCommand.CommandText = "StoredProcedured_UpdateFoo";
SqlDbCommand.Parameters.Clear();
SqlDbCommand.Parameters.Add("#FooData", SqlDbType.DateTime); SqlDbCommand.Parameters["#FooData"].Value = System.DateTime.Now.ToString("yyyy-MM-dd");
myReader = SqlDbCommand.ExecuteReader();
if (myReader.Read())
{
if (int.Parse(myReader.GetValue(0).ToString()) == -1) throw new ErrorDataTxRx("Error FOO ");
}
} finally {
if (SqlDbConnection.State != ConnectionState.Closed){
SqlDbConnection.Close();
}
if (myReader != null){
if (!myReader.IsClosed) myReader.Close();
}
}
I also see part of the same code, checking the same thing using System.Data.DataSet() with Fill method.
Is there a more elegant way to check if the returned value is -1?
Is it OK to use an ExecuteReader in this case?
Have you tried using ExecuteScalar? That's designed for queries which return a single value:
Executes the query, and returns the
first column of the first row in the
result set returned by the query.
Additional columns or rows are
ignored
As Jon correctly noted, you're actually not using the return value of the SP, but actually getting the first value of the first row, which is what ExecuteScalar would do in a simpler way.
However, in order to get the return value from the SP (e.g. from something like RETURN #i; in the SP SQL code), you need to add a new Parameter and set its direction to ReturnValue, something like this:
SqlParameter returnValueParam = new SqlParameter();
returnValueParam.DbType = DbType.Int32;
returnValueParam.IsNullable = false;
returnValueParam.Direction = ParameterDirection.ReturnValue;
SqlDbCommand.Parameters.Add(returnValueParam);
// execute SP here...
int returnValue = (int)returnValueParam.Value;
Use ExecuteNonQuery instead of the reader and you will get the number of rows affected.
Your question is not clear enough. You may need to do it with a ReturnValue like Lucero has written.