I'm taking data that is in a List of Record objects and putting their contents in to a database:
// Processes a Record and adds it to the database
public bool addRecord(SqlConnection db, List<Record> recordsToAdd)
{
using (SqlCommand command = db.CreateCommand())
{
foreach (Record record in recordsToAdd)
{
// Set the query command text
command.CommandText = #"INSERT INTO SMDGROUP_STPRODMASTER (PRODCODE, TOTFREE, TOTPHYS, ITEMTYPE, PRODESC) VALUES ('#PRODCODE', '#TOTFREE', '#TOTPHYS', '#ITEMTYPE', '#PRODESC')";
SqlParameter param1 = new SqlParameter("#CURSTAT", record.curstat);
SqlParameter param2 = new SqlParameter("#ITEMDESC", record.itemdesc);
SqlParameter param3 = new SqlParameter("#PRODCODE", record.prodcode);
SqlParameter param4 = new SqlParameter("#TOTFREE", record.totfree);
SqlParameter param5 = new SqlParameter("#TOTPHYS", record.totphys);
SqlParameter param6 = new SqlParameter("#ITEMTYPE", record.itemtype);
SqlParameter param7 = new SqlParameter("#PRODESC", record.proddesc);
command.Parameters.Add(param1);
command.Parameters.Add(param2);
command.Parameters.Add(param3);
command.Parameters.Add(param4);
command.Parameters.Add(param5);
command.Parameters.Add(param6);
command.Parameters.Add(param7);
// Execute the query
command.ExecuteNonQuery();
}
return true;
}
}
Here's my Record class:
class Record
{
public string curstat { get; set; }
public string itemtype { get; set; }
public string itemdesc { get; set; }
public string prodcode { get; set; }
public string proddesc { get; set; }
public string totfree { get; set; }
public string totphys { get; set; }
}
Just from looking at the code, I've got a feeling that there is a shorter way of achieving this.
But secondly, I'm not even sure if I've done it correctly that the #PARAMETER values are being replaced.
If I view the contents of command, it still shows the query string with the # parameters.
Also, I'm getting this error on command.ExecuteNonQuery():
String or binary data would be truncated.
The statement has been terminated.
So, my questions are:
Is there a shorter way to set and add multiple parameters to the query?
What could be causing the error?
You have a bigger constructor:
command.Parameters.Add(
"#CategoryName", SqlDbType.VarChar, 80).Value = "toasters";
Using the method AddWithValue will make the code a little bit shorter:
command.Parameters.AddWithValue("#CURSTAT", record.curstat);
//...
I do it a bit differntly.
I have both a extension method and a static method to create SqlParameters.
public static SqlParameter ToParam(this object v,string name){
return new SqlParameter(name,v);
}
Then I do something like this:
var p = new List<SqlParameter>();
p.Add(record.curstat.ToParam("#curstat"));
p.Add(record.itemdesc.ToParam("#itemdesc"));
//etc...
command.Parameters.AddRange(p.ToList());
The String or binary data would be truncated. most likely means your putting too many characters into one of your VARCHAR fields. I.e., if your column PRODDESC is a VARCHAR(50), and the string you're trying to insert is 70 characters, you will see that error.
Others have addressed alternative ways of doing the parameters so you can reduce the lines of code.
For a shorter syntax, you can use AddRange method of the SqlParameterCollection class. It means:
command.Parameters.AddRange(new [] {
new SqlParameter(...),
new SqlParameter(...),
new SqlParameter(...) });
The error you're getting indicates that a string value doesn't fit in the table column or parameter, and is being truncated. You should check the length of the column in comparison to the data being inserted, or specify the length of parameters using another overload of the SqlParameter constructor.
If you wanted to use the following class:
Class MyParam
{
public string name {get;set;}
public object value {get;set;}
}
then you could have a List called myParams and do:
foreach(var p in myParams) command.Parameters.AddWithValue(p.name, p.value);
You obviously have to link the parameters and values somehow and there's no way around that. But if you do it in a class like this then the code that actually does the action is only one line long.
I think the message
String or binary data would be truncated.
The statement has been terminated.
comes from an error in your Command Text: in the SQL Query the parameters, even if they are strings, do not need to be quoted.
Replace the command with this
command.CommandText = #"INSERT INTO SMDGROUP_STPRODMASTER
(PRODCODE, TOTFREE, TOTPHYS, ITEMTYPE, PRODESC)
VALUES (#PRODCODE, #TOTFREE, #TOTPHYS, #ITEMTYPE, #PRODESC)";
To shorten the code i think you could add somewhere (for example in your record class or in an helper class) a method that creates an array of parameter from a record object and then call the AddRange function. It should keeps this function cleaner and you could use it also in other part of you code.
In regards to the error, it's a truncation problem i.e. the length of your parameter is longer than what your column can hold. To resolve this, be more specific when passing your parameters e.g. new SqlParameter("#MyParameter", SqlDbType.VarChar, 30).
Personally I don't think their is anything wrong with how your currently adding the parameters it's readable and does the job. If, however, you want to reduce the amont of lines of code in your function you could either go with what #Royi has suggested or simply package up the parameter adding into another method.
This worked for me:
comando.Parameters.Add(
"#EmailAddress", SqlDbType.VarChar).Value = EmailAddress;
Related
I try to read sql table and load all into a variable
Code:
String query2 = "";
query2 = String.Format("SELECT * FROM Seguridad.UsuarioPerfil WHERE UsuarioID = {0}", UsuarioID);
SQLService sqlservice2 = new SQLService();
DataTable reader2 = sqlservice.Leer(query2);
I want to store all data into a variable var tmpPerfiles as object.
I can do something like:
var tmpPerfiles ="";
foreach (DataRow row in reader.Rows)
{
tmpPerfiles = row["UsuarioId"].ToString();
tmpPerfiles = row["PerfilId"].ToString();
}
But I canĀ“t call tmpPerfiles two times. How can I achieve that? Regards
Okay, first up: STOP! Do not EVER write SQL queries like this. SQL Injection Attack is still the #1 cause of security breaches and vulnerabilities (per OWASP), and it's exclusively caused by people writing SQL statements like this.
Never ever write SQL statements like:
statement = "SELECT something from sometable where " + someVar ...
... because all it takes is for that 'somevar' to have an apostrophe and some malicious hacking code, and you're granting an external entity access to your database. Don't even do it if you're not expecting the field to be user-provided or such - it's a bad habit, and it leads to horrendous security faults.
Instead, you should always use one of the following:
Stored Procedures with parameterized inputs. Aka, dbo.usp_FindUser,
which accepts #userName, and the proc has WHERE name = #userName
Parameterized Sql Command. Aka, creating a SqlCommand with "Select *
from something from someTable where userName = #userName", and then
adding a parameter to the SqlCommand of userName, and a value of what
user you're looking for.
Okay, all that said?
Keep in mind, a variable can contain a grouping of things. Generally, if you're looking to contain a table within a single variable? It'll typically look something like:
string x, int y, string z - fields within the Database
Class dataRecord - a class, which contains string x, int y, string z.
List<dataRecord> - a list of instances of a dataRecord class
... make sense? You've got one variable per column, which you group into a class. One instance of the class represents one data row. And then a List<> of that class represents multiple rows of that table (or just the whole table itself.)
Usually, code that follows SRP (but that doesn't use EntityFramework) will look something like:
List<myFancyClass> tableEntries = new List<myFancyClass>();
foreach (DataRow dr in myDataTable.Rows)
{
myFancyClass line = new myFancyClass(dr); // constructor that takes in a DataRow
tableEntries.Add(line);
}
... at that point, the table is stored in the tableEntries variable.
e.g.
use a dto for storing it into a list
private class TmpDto {
string UsuarioId { get; set;}
string PerfilId { get; set;}
}
var Ilist<TmpDto> list = new List<TmpDto>();
foreach (DataRow row in reader.Rows)
{
var dto = new TmpDto();
dto.UsuarioId = row["UsuarioId"].ToString();
dto.UsuarioId = row["PerfilId"].ToString();
list.Add(dto);
}
so you have several objects stored within a list
If you are using EntityFramework, this is really easy. After setting up a class for the object (containing all fields), just use linq to get the object.
var myData = UsuarioPerfil.Where(e => e.UsuarioID == UsuarioID).FirstorDefault();
If all you want to do is get values from a database and put them into something, then I think the introduction of a datatable is overkill. While the implementation is easy enough, it adds overhead. I'd opt to use a DbDataReader instead.
This is an example of extracting a single field, and then just adding it to a list.
List<string> results = new List<string>();
String query2 = "SELECT PerfilId FROM Seguridad.UsuarioPerfil WHERE UsuarioID = #USARIO";
SqlCommand cmd = new SqlCommand(query2, connection);
cmd.Parameters.Add(new SqlParameter("#USARIO", SqlDbType.VarChar));
cmd.Parameters[0].Value = UsuarioID;
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
results.Add(reader.GetString(0));
}
reader.Close();
You referenced two fields, but since UsuarioID was defined in the where clause, it didn't seem necessary to pull it back.
Also, as #DotNetDev mentioned, don't use literals... the use of parameters is not only SQL-Injection safe, but it's more scalable and actually friendlier to the database (compile-once, execute many).
Finally, if you want to pull back multiple fields, create a data (domain) object, and make your results a list of that object. If you need an example, feel free to ask.
I'm writing some code that attempts to loop through a list of recipient ids, but with the same message id and owner id for each recipient id and at each iteration, execute a stored procedure. In testing, I keep getting the
aforementioned error.
Column name or number of supplied values does not match table
definition
Now I've tested the data extensively at the database level, the stored procedure is working just fine, so I'm reasonably sure its here in the C# code. I've made sure the parameter names are exact, and that they are the same corresponding types, etc. I've tested with the same sample data. My object model looks like this:
public class ShareModel
{
public string MessageId { get; set; }
public int OwnerId { get; set; }
public List<int> RecipientIds { get; set; }
}
And my method:
public void ShareMessage(ShareBindingModel shares)
{
try
{
var msgId = shares.MessageId;
var ownerId = shares.OwnerId;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["conn"].ConnectionString))
{
conn.Open();
foreach (var i in shares.RecipientIds)
{
using (SqlCommand command = new SqlCommand("ForwardMessage", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#new_recipient_id", i);
command.Parameters.AddWithValue("#src_msgid", msgId);
command.Parameters.AddWithValue("#fwdperson_id", ownerId);
command.ExecuteNonQuery();
}
}
}
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
}
Stored procedure (I did not write this):
ALTER PROCEDURE [dbo].[ForwardMessage]
#new_recipient_id int,
#src_msgid varchar(30),
#fwdperson_id int
AS
BEGIN
declare #new_msgid varchar(30)
declare #tenant_id int
declare #sender_id int
declare #action_dt datetime
select #tenant_id=tenant_id,
#sender_id=sender_id,
#action_dt=getdate()
from message
where msg_id=#src_msgid
exec dbo.GenerateString 1,1,1,null,30,#new_msgid OUT
insert into message
SELECT #new_msgid
,[tenant_id]
,[sender_id]
,#new_recipient_id
,(select email_addr from person_email where person_id=#new_recipient_id )
,[description]
,[date_recorded]
,[filename]
,[size_bytes]
,[size_time]
,[filepath]
,[Flag]
,[title]
,[date_activity]
FROM [message]
where tenant_id=#tenant_id
and msg_id=#src_msgid
END
If anyone could point out what I'm doing wrong I would appreciate it.
Usually either you're not calling the stored procedure you think you are (try to specify the schema too) or the parameters required by the stored procedure are different from the ones you're supplying.
The error Column name or number of supplied values does not match table definition points to a problem when inserting to a table. As your only INSERT is the insert into message... from your stored procedure, I'd wager that that's the problem, and that there is a mismatch between the column list for that INSERT and the columns of the table itself.
(I'm not sure why this would be the case if "the stored procedure is working just fine", as you say, but it's the only possible answer given the evidence.)
I have a stored procedure that returns two columns without specifying their name, one is the ID (int) and the other one is a string. It is not possible for me to alter the stored procedure so that it can return the results with column names
If I let Visual Studio to create the .edmx file with the stored procedures in the dbContext, it creates a new model class with two properties called Column1 and Column2. The problem is that when I run it, I receive the following error:
The data reader is incompatible with the specified 'Schema.addCliente_Result'. A member of the type, 'Column1', does not have a corresponding column in the data reader with the same name.
Is there any other way to map the result to maybe a a dictionary<int,string> or anything else that does not need the column name?
You can try using KeyValuePair. It saves the value specified for your key. You can create list of KeyValuePair if you have more pairs and you want to iterate through it easier. I'm not sure if this helps in your case, sorry if it's not helping.
KeyValuePair MSDN
Perhaps you may want to consider projection since you don't want it mapped to a Model. Projection is when the result of a query is output to a different type than the one queried. Either way, you can use the results of the stored procedure to either create an anonymous type or a class of your own. Since there's no code for me to reference, I have an example as follows:
Class used to store results
public Class Result
{
public int myID {get; set;}
public string myString {get; set;}
}
Code to call stored procedure
string connectionString = ConfigurationManager.AppSettings["MyDatabase"];
var conn = new SqlConnection(connectionString);
conn.Open();
string query = #"my_stored_procedure";
using (var sqlAdpt = new SqlDataAdapter(query, conn))
{
sqlAdpt.SelectCommand.CommandType = CommandType.StoredProcedure;
// Ex: Parameters if your sp takes one
var dataDate = new SqlParameter { ParameterName = "#DataDate", Value = DateTime.Now };
sqlAdpt.SelectCommand.Parameters.Add(dataDate);
var results = new DataSet();
sqlAdpt.Fill(results);
List<Result> resultList = results.Tables[0].AsEnumerable().
Select(dataRow => new Result
{
myID = dataRow.Field<int>("ID"),
myString = dataRow.Field<string>("column_I_cant_change")
}).ToList();
}
I have a conceptual Yes / No question. I am executing a stored procedure that returns one row. Is there a method similar to ExecuteScalar() that will allow me to populate variables directly with the column values? Or is using a DataReader/DataGrid/Dataset the only process to do it?
No. Whether its one row or 1000, you (or MS internal code) will use a DataReader.
If you're interested in super minutiae super performance, declare a whole bunch of "out" parameters and hydrate from that.
But 99% of the time, its not worth the headache (the out parameters that is)
Suppose you have a class called User where the properties of the class match directly the field names returned by your stored procedure. If this is the case, then directly from the Dapper examples
public class User
{
public int UserID {get;set;}
public string Account {get;set;}
public string Pass {get;set;}
......
}
using(SqlConnection cnn = GetConnection())
{
User u = cnn.Query<User>("spGetUser", new {Id = 1},
commandType: CommandType.StoredProcedure).First();
......
}
How do i convert a Request.Query string to an integer value. I've tried all the Convert.ToInt32 and Int32.Parse but it says Input string is not in the correct format. I am using the string value as an input to a stored procedure which takes in only integer types for that field.
Here's a part of the code-
string rid=Request.QueryString["RID"];
lblRID.Text = rid;
int id= Int32.Parse(rid);
if (lblRID.Text!= null)
SqlCommand myCommand = new SqlCommand("usp_NewResource_get", myConnection);
myCommand.Parameters.AddWithValue("#RID",id); //RID is int in database and stored procedure
myCommand.CommandType = CommandType.StoredProcedure;
int foo;
int.TryParse(Request.QueryString["foo"], out foo);
or just like you say, int.Parse should convert to int
Could you post some code here ?
The answer thesedays varies based on what framework you're using as msft has made query params part of the view attribute model now for binding (ref: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute?view=aspnetcore-3.1).
You can still access most things via httpcontext for the sake of example.
var fooIsInt = int.TryParse(HttpContext.Request.Query["foo"], out var foo);
Original Example for webforms in .net 2.0
Quick and dirty (and in a page load because this is an example, you should be able to work out what's happening from this)
<script runat="server">
private void Page_Load(object sender, System.EventArgs e){
string test = Request.QueryString["foo"];
//Convert the query string you captured to an int and store it in an int.
int intTest = Convert.ToInt32(test);
Response.Write(intTest.GetType() + "<br>" + intTest);
}
</script>
How about looking on the input that you provide to Int32.Parse?
We use a base class from which every Page inherits. We have the following method that returns integer values from querystring params:
protected int FetchQueryStringIdentifierByKey(string key)
{
int identifier;
var isInt = int.TryParse(Request.QueryString[key], out identifier);
return (isInt) ? identifier : 0;
}
Credit goes to Jayson Knight for his research into his original bench-marking results under .NET 2.0.
string id = Request.QueryString["RID"].ToString();
userid=Convert.ToInt32(id);
How about using TryParse like this:
string rid = Request.QueryString["RID"];
int id = 0;
if (!string.IsNullOrEmpty(rid) && Int32.TryParse(rid, out id))
{
lblRID.Text = rid;
SqlCommand myCommand = new SqlCommand("usp_NewResource_get", myConnection);
myCommand.Parameters.AddWithValue("#RID",id);
myCommand.CommandType = CommandType.StoredProcedure;
myCommand.Execute...
}
The problem is that the value passed in the query string with the RID parameter is not a number, so it cant be parsed to an int. E.g you have ?RID=hello or maybe no RID parameter at all (in which case the value is null). Inspect the querystring to see the actual value passed.