How can I implement this T-SQL in my stored procedure? - sql-server

I'm new to SQL Server and want to implement this scenario. My stored procedure gets 8 input parameters from a C# web application, and checks all input has into the table. For that purpose I wrote this simple stored procedure:
CREATE PROCEDURE CheckValid
#p_bank varchar,
#p_pay_date varchar,
#p_bill_id varchar,
#p_payment_id varchar,
#p_ref_code varchar,
#p_branch varchar,
#p_channel_type varchar,
#p_send_date varchar
AS
BEGIN
SELECT
[p_bank], [p_pay_date], [p_bill_id], [p_payment_id],
[p_ref_code], [p_branch], [p_channel_type], [p_send_date]
FROM
[SAMPLE].[dbo].[MixedTable]
WHERE
[p_bank] = #p_bank
AND [p_pay_date] = #p_pay_date
AND [p_bill_id] = #p_bill_id
AND [p_payment_id] = #p_payment_id
AND [p_ref_code] = #p_ref_code
AND [p_branch] = #p_branch
AND [p_channel_type] = #p_channel_type
AND [p_send_date] = #p_send_date
END
But want to return to c# application this scenario, for example c# sends all field but when stored procedure select run for this purpose can not find data, for example p_bill_id not correct into the table for more explain in select query into where clause in the [p_bill_id]=#p_bill_id not trust and now want to return sp this :
p_bill_id,not found
and other example c# all variable correct but two field [p_channel_type] and [p_payment_id] not correct into where clause but other 6 field correct now SP return this:
[p_channel_type],not found
[p_payment_id],not found
Summary of question:
When data for passed parameter value is not found, I want it to return that corresponding column.
For example:
[p_channel_type],not found
[p_payment_id],not found

Note, varchar means varchar(1) so you should specify length for each argument explicitly like varchar(100)
CREATE PROCEDURE CheckValid
#p_bank varchar(<length>),
#p_pay_date varchar(<length>),
#p_bill_id varchar(<length>),
#p_payment_id varchar(<length>),
#p_ref_code varchar(<length>),
#p_branch varchar(<length>),
#p_channel_type varchar(<length>),
#p_send_date varchar(<length>)
AS
BEGIN
if not exists(select 1 from dbo.BankTable where p_bank = #p_bank)
begin
raiserror('Bank %s not found', 16, 1, #p_bank)
return
end
if not exists(select 1 from dbo.BillTable where p_bill_id = #p_bill_id)
begin
raiserror('Bill %s not found', 16, 1, #p_bill_id)
return
end
...
SELECT [p_bank],[p_pay_date],[p_bill_id],[p_payment_id],[p_ref_code],[p_branch],[p_channel_type],[p_send_date]
FROM [SAMPLE].[dbo].[MixedTable]
where [p_bank]=#p_bank and [p_pay_date]=#p_pay_date
and [p_bill_id]=#p_bill_id and [p_payment_id]=#p_payment_id
and [p_ref_code]=#p_ref_code and [p_branch]=#p_branch
and [p_channel_type]=#p_channel_type and [p_send_date]=#p_send_date
END
GO

Instead of creating stored procedure for this move "validation" logic to your c# application.
Database is just IO device and I think keeping "business logic" in IO device not a good approach.
// Class which represent your eight parameters
public class Data
{
public string Bank { get; set; }
public string PayDate { get; set; }
public string BillId { get; set; }
public string PaymentId { get; set; }
public string RefCode { get; set; }
public string Branch { get; set; }
public string ChannelType { get; set; }
public string SendDate { get; set; }
}
public class Validation
{
private Data _data;
public Validation(Data data)
{
_data = data;
}
public IEnumerable<string> Validate()
{
var columns = new KeyValuePair<string, string>[]
{
new KeyValuePair("p_bank", _data.Bank),
new KeyValuePair("p_pay_date", _data.PayDate),
new KeyValuePair("p_bill_id", _data.BillId),
new KeyValuePair("p_payment_id", _data.PaymentId),
new KeyValuePair("p_ref_code], _data.RefCode),
new KeyValuePair("p_branch", _data.Branch),
new KeyValuePair("p_channel_type", _data.ChannelType),
new KeyValuePair("p_send_date", _data.SendDate)
};
return columns.Where(pair => IsValueExists(pair.Key, pair.Value) == false);
}
private bool IsValueExists(string columnName, string value)
{
var query =
$"SELECT [{columnName}]
FROM [SAMPLE].[dbo].[MixedTable]
WHERE [{columnName}] = #value";
var parameter = new SqlParameter
{
ParameterName = "#value",
SqlDbType = SqlDbType.VarChar,
Value = _data.Bank
};
using (var connection = new SqlConnection(yourConnectionString))
using (var command = new SqlCommand(query, connection))
{
command.Parameters.Add(parameter);
connection.Open();
var value = command.ExecuteScalar();
return value != null; // null returned if no rows exists
}
}
}
Then you can use this method somewhere
var data = new Data { Bank = "BankName", RefCode = "SomeRefcode" } // put all values
var validation = new Validation(data);
var invalidValues = validation.Validate();
foreach(var invalidValue in invalidValues)
{
// Print or save column names where value is invalid
}

Related

How do I call stored procedures in EF Core 6 using named parameters with FromSqlRaw

I want to execute a stored procedure with a minimum of 3 to 20 parameters.
I am converting the property values from a request model to an SQL string and a list of parameters. Then make the call and expect a result but when I check the Created SQL from debug view, I observe that the EF tries to call the sp with non-named style parameters.
For brevity assume the first 3 properties contain values, and other properties have null values.
RequestModel
{
string Property1 { get; set; }
string Property2 { get; set; }
string Property3 { get; set; }
...
}
ResponseModel
{
string Response1 { get; set; }
string Response2 { get; set; }
...
}
var sqlString = "exec sp_dummy #prop1, #prop2, #prop3";
var params = new SqlParameter[] { new SqlParameter("#prop1", Property1), new SqlParameter("#prop2", Property2), new SqlParameter("#prop3", Propery3) };
return await ResponseModel.FromSqlRaw(sqlString, params).ToListAsync();
And when I check the generated SQL from EF Core, it is as follows:
DECLARE #prop1 nvarchar(10) = N'Prop1Data';
DECLARE #prop2 nvarchar(10) = N'Prop2Data';
DECLARE #prop3 nvarchar(10) = N'Prop3Data';
exec dummy_sp #prop1, #prop2, #prop3
What I want to achieve is as follows:
exec dummy_sp #prop1 = N'Prop1Data' , #prop2 = N'Prop2Data', #prop3 = N'Prop3Data'
I have achieved this by using dbcontext's ExecuteSqlRaw method, instead of FromSqlRaw extension method.
Database.ExecuteSqlRaw(sql,params);

not mapped value doesn't return correct

I'm coding an ASP .NET Core Web API Project and have an issue about a not mapped prop in my model
Here's my model
public class Category
{
[Key]
public SqlHierarchyId NodeId { get; set; }
[Required, MaxLength(200)]
public string Name { get; set; }
[Required]
public int CategoryId { get; set; }
[NotMapped]
public int ChildCount { get; set; }
}
and the procedure
ALTER PROC [dbo].[GetCategoryChild](#categoryId int)
AS
BEGIN
DECLARE #CurrentNode hierarchyid
SELECT #CurrentNode = NodeId FROM Categories
WHERE CategoryId = #categoryId
SELECT *
,(select count(*) as cc from Categories where NodeId.GetAncestor(1) = ht.NodeId) as ChildCount
FROM Categories as ht
WHERE NodeId.GetAncestor(1) = #CurrentNode
END ;
when I execute my procedure in SQL server returns expected result
NodeId Name CategoryId ChildCount
------------------------------------------------
0x58 Books 19 2
0x8C Computers 25 1
but when I call it in Controller,all ChildCounts are 0
NodeId Name CategoryId ChildCount
------------------------------------------------
0x58 Books 19 0
0x8C Computers 25 0
and here's my controller
[HttpGet, Route("getChild/{categoryId}")]
public IEnumerable<Category> GetChild(int categoryId)
{
var childs = _db.Categories.
FromSql($"GetCategoryChild {categoryId}").ToList();
return childs;
}
Build a sql command and add the categoryId variable as a sqlcommand parameter. That’s how you should be calling sprocs from application code.
You can remove ChildCount property from Category model, and create a view model CategoryViewModel and map result from stored procedure to this view model:
var param = new SqlParameter("#categoryId", categoryId);
var result = dbContext.Database
.SqlQuery<CategoryViewModel>("dbo.GetCategoryChild #categoryId",param).ToList();
Please see this Link:
Execute stored procedure in entity Framework Core without expecting map to dbset

Dapper return specfic field from query

I am working on connecting to SQL database and setting variables in my C# application to match returned values from a Dapper Query.
I am able to return the correct row information (I used a datagridview to show that i get the correct row, and when i debug i see the right data) but how do I set a program variable to just one of the columns? here is some code showing my process
Connect & run stored procedure:
using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
List<User> userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>();
Variables.UserID = txt_userid.Text.Trim();
datagridview1.DataSource = userinfo; //this displays the right information, however I want to store the information as a public variable(like above)
Variables.Userfull = userinfo; //when debugging this shows I have the right information but its all columns of user
User Class:
class User
{
public string UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int AccessLevel { get; set; }
public int FirstUse { get; set; }
public string Password { get; set; }
public int LoginCounter { get; set; }
public string LastLogin { get; set; }
public string FirstLogin { get; set; }
public string UserFullName { get; set; }
}
EDIT
per answer, I have tried to switch the command to executescalar with this code:
var userinfo = conn.ExecuteScalar<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure);
This allows me to set
Variables.UserID = userinfo.UserID;
without red-squiggle lines however during run-time i get an error on the execute scalar of "System.InvalidCastException: 'Invalid cast from 'System.String' to 'Name_Space.User'.'
I have checked the User Class to ensure the data type matches with the Database and i see no problems there, I'm not sure what I'm doing wrong?
If you want value from only one column of only one row, use ExecuteScalar which is similar to ADO.NET ExecuteScalar. It will return object with which you have to deal further.
string sql = "SELECT COL1 FROM Table1 WHERE ID = 1";
//OR
//string sql = "SELECT TOP 1 COL1 FROM Table1";
//OR similar
object colValue = conn.ExecuteScalar(sql, ....);
If matching record not found, return value will be null.
Check Dapper documentation for other generic variants of method:
public static T ExecuteScalar<T>(this IDbConnection cnn, CommandDefinition command);
If you want single column from multiple rows, the way you are doing this now is correct. Just modify your SQL query/Stored Procedure to return the same. Dapper will only map returned column. All other properties in your User will remain unassigned.
If you simply want to assign values to Variables.UserID instead of binding those using DataSource, then just do that like:
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
//and so on....
So, complete code will be something like below:
using(SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
User userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>().First();
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
}

Mapping fields in stored procedure to class properties with different names with Dapper

I have this exert from a POCO class with many fields:
public class Call
{
public int Id { get; set; }
public string Customer { get; set; }
public int StatusId { get; set; }
public int UserAssignedToId { get; set; }
public string UserAssignedToName { get; set; }
}
However my stored procedure returns different names to the properties above (in this case the Id is before:
IdCall
IdStatus
IdUserAssignedTo
This is the code I am using to execute the stored procedure:
var call = conn.Query<Call>("CallSPName", new { IdCall = callId }, commandType: CommandType.StoredProcedure).First();
How can I specify a mapping to say I would like "IdStatus" from my stored procedure map to "StatusId" in my POCO class and "IdCall" to "CallId" etc?
I don't have access to change the stored procedures as they are controlled by DBAs and older legacy systems are using them which would break if the fields got changed in the stored procedure.
Any ideas/thoughts appreciated.
The closest thing which comes to my mind is to have private properties mapped to columns returned by the stored procedure and make the public properties with the names you want setting and getting those private fields:
// ...
private int IdStatus;
public int StatusId {
get { return IdStatus; }
set { IdStatus = value; }
}
// ...

Dapper fails when converting int to bool (Sqlite)

I have a table with IsValid column, with int datatype (Sqlite).
When selecting from it Dapper fails:
{"Error parsing column 0 (IsValid=1 - Int32)"}
{"Specified cast is not valid."}
Which makes sense, but it has to be a pretty common case - shouldn't it be accounted for ?
This is the code:
public void Test()
{
string conns = #"Data Source=mydbfile.db3;Version=3;";
string sql = null;
using (SQLiteConnection connection = new SQLiteConnection(conns))
{
connection.Open();
sql = "INSERT INTO Test (IsValid) VALUES (1)";
connection.Execute(sql);
sql = "SELECT * FROM Test";
var x = connection.Query<Valid>(sql);
}
}
private class Valid
{
public bool IsValid { get; set; }
}
Table has a single 'IsValid' column of type int.

Resources