Lambda match on partial object - c#

I am using dapper and a lambda expression in order to retrieve data from the database. Instead of building a ton of overloaded expressions, i want to pass in a single database object and have my lambda expression match on the closest or first object found.
public static User GetUser(User pUser)
{
using (IDbConnection connection = new SqlConnection(Connection))
{
return connection.Query<User>("SELECT * FROM dbo.USERS").(m => m == pUser);
}
}
In the example above, you can see I am passing a "User" object into the function, this user object could be 50% of what the exepected object is. for example, if the object had 2 strings, the ID and the users name. But I only knew the users name. I would create a new user as an ref or out param and have the query fill in the missing data.
Thoughts? I could create a ton over overloaded functions with duplated code like GetUserByID, and GetUserByName but that seems redundant.

You need to re-write the query. Kindly provide the structure of dbo.Users table.
Place filter logic inside of query and use table columns to match records instead of whole object.
public static User GetUser(User pUser)
{
using (IDbConnection connection = new SqlConnection(Connection))
{
return connection.Query<User>("Select * FROM dbo.Users where userID = #UserId", pUser).FirstOrDefault();
}
}

Related

SQL multiple-value parameter in C#

This is what I have:
select *
from AuditQuestionnaires
where Companyid = #companyid
and RiskTypeID != #RiskIdList[x];
Why can't I add a parameter like this:
command2.Parameters.Add(new SqlParameter("RiskIdList[x]", RiskIdList[x]));
I'm using SQL Server, and this is the script I'm using in .cs file
Firstly, since you are using SQL Server, you cannot have a parameter named #RiskTypeId[x]. It is simply not a valid name.
Secondly, when passsing parameters from c# the parameter name has to include the initial "#".
Thirdly, as a general rule it is much better to construct the SQLParameter including the DataType. This means that there is no guesswork required, and guesses occasionally go wrong.
Fourthly, it seems to me that you want to iterate through an array. There is nothing to stop you doing this in c#. You would simply create a SqlParameter like this
command2.Parameters.Add(new SqlParameter { ParameterName = "#RiskTypeId", DBType = DBType.Int32, Value = RiskIDList[x] });
However I suspect that what you are really trying to do is to send a list to SQL Server. To achieve this, you need to use a User-Defined Table Type. In your case the following is sufficient:
CREATE TYPE [dbo].[IdList] AS TABLE ([Id] int);
Then you can define a parameter to your query as:
#RiskTypeIdList IdList READONLY
Finally you have to put the contents of your array into a DataTable. Because this is something I do frequently, I have a Class for this job:
public class TDataTable<int> : DataTable
{
public TDataTable(IEnumerable<T> ids) : base()
{
Columns.Add("Id", typeof(T));
var added = new HashSet<T>();
foreach (T id in ids)
{
//ensure unique values
if (added.Add(id))
{
Rows.Add(id);
}
}
}
public TDataTable() : base()
{
Columns.Add("Id", typeof(T));
}
}
You can then pass the parameter like this:
command2.Parameters.Add(new SqlParameter { ParameterName = "#RiskTypeIdList", SqlDBType = SqlDBType.Structured, Value = new TDataTable<int>(RiskIDList) });
Within your SQL you can then do what Thorsten suggested:
WHERE RiskTypeId NOT IN (SELECT Id FROM #RiskTypeIdList)
#Thorsten Kettner, its a valid SQL query, we can != in sql query.
command2.Parameters.Add(new SqlParameter("RiskIdList[x]", RiskIdList[x]));
We cannot pass "RiskIdList[x]" as SQL Parameter, we need to pass parameter like this "RiskIdList", RiskIdList[x] means that you are passing argument at runtime, but query or stored procedure has not dynamic parameter in where clause.

How to read select query on mysql db using c#

I have a C# based api and I send queries to a mysql server. I wonder how can i read the id from a select to a table on C# Note that I am using MySql.Data.MySqlClient;
My code until the execute is this one below. But in this step I wonder how can I retrieve the desired id. I used ExecuteNotQuery but it seems it does not fit on what I need.
string connectionString = #"server=x.x.x.x;userid=xxxx;password=xxxxxx;database=testdatabase";
string getLastStoryIdQuery = "SELECT MAX(ID) FROM testdatabase.test";
MySqlCommand getLastTestIdCommand = new MySqlCommand(getLastStoryIdQuery, mySqlConnection);
int lastId = getLastStoryIdCommand.ExecuteNonQuery();
How can I retrieve the result as an Integer or in worst case as a string response? Thank you in advance. :)
int lastId = Convert.ToInt32(getLastStoryIdCommand.ExecuteScalar());
You can find the documentation on MySqlCommand here: https://dev.mysql.com/doc/dev/connector-net/8.0/html/T_MySql_Data_MySqlClient_MySqlCommand.htm
The method ExecuteNonQuery returns the number affected by the query, while ExecuteScalar returns the first column of the first row. You can also use ExecuteReader to get a datareader so that you can read a resultset the database produces.
In practice, I rarely use DbCommand/DbReader anymore and prefer to just use Dapper for database access in most cases where performance isn't absolutely critical. It simplifies parameter creation, and object filling which serves the vast majority of my use cases.
Dapper would look like this:
string connectionString = #"server=x.x.x.x;userid=xxxx;password=xxxxxx;database=testdatabase";
string getLastStoryIdQuery = "SELECT MAX(ID) FROM testdatabase.test";
int lastId;
using(var conn = new MySqlConnection(connectionString))
{
lastId=conn.Query<int>(getLastStoryIdQuery).First();
// you can also do the following in this instance, but you will use the
// above for results that return multiple rows or multiple columns
//lastId=conn.ExecuteScalar<int>(getLastStoryIdQuery);
// Here is how you use parameters:
// var something = conn.ExecuteScalar<int>("SELECT id FROM testdatabase.test WHERE id=#param",new {param = 10});
// This gets multiple columns and rows into a List<person> (assuming you have a person class with fname,lname,dob properties):
// var people = conn.Query<person>("SELECT fname,lname,dob FROM persons WHERE dob>#start", new {start=new DateTime(2000,1,1)}).ToList();
}

handling large amounts of data to include in an oracle select statement

Recent bug report states that a method being called is crashing the service causing it to restart. After troubleshooting, the cause was found to be an obnoxious Oracle SQL call with thousands of strings passed. There is a collection of strings being passed to a method from an external service which often is more than 10,000 records. The original code used a where clause on the passed collection using the LIKE keyword, which I think is really, really bad.
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
Clarification of in house methods used which may be confusing:
DataBase.SelectQuery is an internal library method using generics which gets passed the sql string, a function to map the records to .NET objects, and the parameters being passed and returns an IEnumerable of Objects of type retuned by the Mapping function.
SliceLeft is an extension method from another internal helper library that just returns the first part of a string up to the number of characters specified by the parameter.
The reason that the LIKE statement was apparently used, is that the strings being passed and the strings in the database only are guaranteed to match the first 10 characters. Example ("XXXX000000-1" in the strings being passed should match a database record like "XXXX000000-8").
I believed that the IN clause using the SUBSTR would be more efficent than using multiple LIKE clauses and replaced the code with:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
This helped slightly, and there were fewer issues in my tests, but when there are huge amounts of records passed, there is still an exception thrown and core dumps occur, as the SQL is longer than the server can parse during these times. The DBA suggests saving all the strings being passed to a temporary table, and then joining against that temp table.
Given that advice, I changed the function to:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
#"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
#containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
Now I'm getting a "ORA-00900: invalid SQL statement". This is really frustrating, how can I properly write a SQL Statement that will put this list of strings into a temporary table and then use it in a SELECT Statement to return the list I need?
There are couple possible places could cause this error, it seams that the "DECLARE GLOBAL TEMPORARY" is a JAVA API, I don't think .net has this function. Please try "Create global temporary table" instead. And, I don't know whether your internal API could handle multiple SQLs in one select sql. As far as I know, ODP.net Command class can only execute one sql per call. Moreover, "create table" is a DDL, it therefore has its own transaction. I can't see any reason we should put them in the same sql to execute. Following is a sample code for ODP.net,
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}

Parse sql parameters from commandtext

Is it possible to parse sql parameters from plain commandtext?
e.g.
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
SqlParameterCollection parCol = sqlc.Parameters //should contain now 1 paramter called '#id'
If a SQL Server is available, the best option may be to simply ask the server what it thinks; the server has parsing and metadata functions built in, for example sp_describe_undeclared_parameters.
I ended up with this extention method (since I don't think there's a built in function):
public static class SqlParExtension
{
public static void ParseParameters(this SqlCommand cmd)
{
var rxPattern = #"(?<=\= |\=)#\w*";
foreach (System.Text.RegularExpressions.Match item in System.Text.RegularExpressions.Regex.Matches(cmd.CommandText, rxPattern))
{
var sqlp = new SqlParameter(item.Value, null);
cmd.Parameters.Add(sqlp);
}
}
}
usage:
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
sqlc.ParseParameters();
sqlc.Parameters["#id"].Value = value;
I will have to make sure about this but I'm sure you must add the range of parameters to the command. Like I say I will have to come back with this but you can try doing something like:
// Create a collection of parameters with the values that the procedure is expecting in your SQL client.
SqlParameter[] parameters = { new SqlParameter("#id", qid),
new SqlParameter("#otherValue", value) };
// Add teh parameters to the command.
sqlc.Parameters.AddRange(parameters)
You would be very welcome to have a look at my VS2015 extension, QueryFirst, that generates wrapper classes from .sql files, harvesting parameter declarations directly from your sql. You need to declare your parameters in the --designTime section of your request, but then you find them again directly as inputs to the Execute(), GetOne() or ExecuteScalar() methods. These methods return POCOs with meaningul property names. There's intellisense everywhere, and you don't have to type a line of parameter code, or connection code, or command code, or reader code, among NUMEROUS OTHER ADVANTAGES :-).

auto SQL field's quotation marks by type in c#

Suppose that I want to create an SQL SELECT statement dynamically with reflection on primary key. I search in the table for primary keys and then, I make the statement.
Problem is, I don't know the type of fields that compose the primary key before getting them. So, if it's a string or date, I must add quotation marks but not if it's an int.
Atm, I am doing like that :
var type = field.GetType().Name;
if (type.ToLower().StartsWith("string") || type.ToLower().StartsWith("date"))
{
field = "\"" + field + "\"";
} else if (type.ToLower().StartsWith("char"))
{
field = "\'" + field + "\'";
}
With this code, I can handle some SQL types but there are a lot more.
My problem is that it's combined with LinQ. I got a DataContext object and a generic type table from the context. And context.ExecuteQuery only allows parameters to be passed has values. I also tried with Dynamic LinQ but I got the same problem
Does anyone know a better solution?
That is simply the wrong way to write SQL. Parameterize it and all these problems evaporate (as do problems with "which date format to use", etc. And of course the biggie: SQL injection.
Then it just becomes a case of adding #whatever into the TSQL, and using cmd.Parameters.AddWithValue("whatever", field) (or similar).
Update (from comments): since you mention you are using DataContext.ExecuteQuery, this becomes easier: that method is fully parameterized using the string.Format convention, i.e.
object field = ...;
var obj = db.ExecuteQuery<SomeType>(
"select * from SomeTable where Id = {0}", field).ToList(); // or Single etc
No string conversions necessary.
(the last parameter is a params object[], so you can either pass multiple discreet terms, or you can populate an object[] and pass that, if the number of terms is not fixed at compile-time; each term in the array maps by (zero-based) index to the {0}, {1}, {2}... etc token in the query)
Have you tried with parameters? For instance if you are using SQLServer as a database and you want to do this query:
"SELECT * FROM someTable WHERE id = " + field;
Then you should use sometething like this:
"SELECT * FROM someTable WHERE id = #field"
and add parameter to your command:
SqlParameter param1 = new SqlParameter("#field", field);
command.Parameters.Add(param1);
EDIT: Watch out that for different database providers the syntax for the SQL query is different, the same for the Access would be
"SELECT * FROM someTable WHERE id = ?";
command.Parameters.AddWithValue("field", field);

Categories