How can i compare an array with sql query in c# code? - c#

How can I compare an array with a SQL query in C#?
I have tried below code to resolve my problem but it was not worked.
In the below code data is the name of array which holds some types of data.
DataTable dt = context.getData("Select * from emp_detail where EState = any(" +data+ ")", CommandType.Text);

You can serialize the data array into a properly formatted string and change select command to something like that:
Select * from emp_detail where EState in ('a', 'b', ...)
This solution is only suitable for arrays with few items.

I think you could do this to get a list of strings to compare with:
using System.Linq;
namespace ExampleNamespase
{
public class SimpleClass
{
private class TypeExample
{
public string Property { get; }
}
private void CompareData(TypeExample[] exampleArray)
{
var items = string.Join(", ", exampleArray.Select(t => t.Property));
var sql = $"Select * from emp_detail where EState in ({items}) ";
}
}
}
Or so if you are using a simple list of strings:
private void CompareData(string[] exampleArray)
{
var items = string.Join(", ", exampleArray);
var sql = $"Select * from emp_detail where EState in ({items}) ";
}
To prevent injections, use the Dapper library:
using System.Data;
using Dapper;
namespace ExampleNamespase
{
public class SimpleClass
{
private void CompareData(string[] exampleArray, IDbConnection connection)
{
var sql = "Select * from emp_detail where EState in #items ";
var result = connection.Query<YourTypeClass>(sql, new { items = exampleArray });
}
}
}
Explanation:
The first example uses the Linq library to retrieve the properties of an object using the Select method, and the string.Join method to concatenate the resulting list into a comma-separated string.
The second example demonstrates the same approach, only with an array of strings, the same string.Join method is used to join.
In the third example, the Dapper library is used to execute the request, I believe that this method should be preferred, since helps to avoid SQL injection, and also simplifies the work with the database. To execute a query, we can pass a list of objects with which we want to compare values ​​in the database, and Dapper will be able to handle this itself. We just need to define variables with the # symbol in the request, and pass the object for processing, which will have attributes with the same names.
P.S. I apologize for my English))
Upd:
Try this:
private void CampareData(string[] exampleArray, IDbConnection connection)
{
var clearItems = exampleArray.Select(s => $"'{s}'").ToArray();
var sql = "Select * from emp_detail where EState in #items ";
var result = connection.Query<YourTypeClass>(sql, new { items = clearItems });
}
In this variant, we will wrap each item in quotes that are used by sql, and the query should contain something like: ... in ('item1', 'item2' ...)

Related

Entity Framework using distinct substring and charindex

I am trying to use SQL queries with Entity framework. But I didn't find out how to use it. Basically I try to use query as follow
SELECT DISTINCT
SUBSTRING(KeyStr, CHARINDEX('TYPE_', KeyStr) + 5, 5) Menu_Item,
Content, level
FROM
DB.dbo.StringDB
You can achieve that using the following code:
Assuming you have a class called Result which has the following declaration
public class Result
{
public string Menu_Item{get;set;}
public string Content{get;set;}
public string level{get;set;}
}
To retrieve the results using SQL Query:
var sql = #"SELECT DISTINCT
SUBSTRING(KeyStr, CHARINDEX('TYPE_', KeyStr) + 5, 5) Menu_Item,
Content, level
FROM
DB.dbo.StringDB";
var results = _dbContext.Database.SqlQuery<Result>(sql);
This should return a list of Result object

DapperExtensions: Add "insert ... update on duplicate key"

Now I'm using Dapper + Dapper.Extensions. And yes, it's easy and awesome. But I faced with a problem: Dapper.Extensions has only Insert command and not InsertUpdateOnDUplicateKey. I want to add such method but I don't see good way to do it:
I want to make this method generic like Insert
I can't get cached list of properties for particular type because I don't want to use reflection directly to build raw sql
Possible way here to fork it on github but I want to make it in my project only. Does anybody know how to extend it? I understand this feature ("insert ... update on duplicate key") is supported only in MySQL. But I can't find extension points in DapperExtensions to add this functionality outside.
Update: this is my fork https://github.com/MaximTkachenko/Dapper-Extensions/commits/master
This piece of code has helped me enormously in MySQL -related projects, I definitely owe you one.
I do a lot of database-related development on both MySQL and MS SQL. I also try to share as much code as possible between my projects.
MS SQL has no direct equivalent for "ON DUPLICATE KEY UPDATE", so I was previously unable to use this extension when working with MS SQL.
While migrating a web application (that leans heavily on this Dapper.Extensions tweak) from MySQL to MS SQL, I finally decided to do something about it.
This code uses the "IF EXISTS => UPDATE ELSE INSERT" approach that basically does the same as "ON DUPLICATE KEY UPDATE" on MySQL.
Please note: the snippet assumes that you are taking care of transactions outside this method. Alternatively you could append "BEGIN TRAN" to the beginning and "COMMIT" to the end of the generated sql string.
public static class SqlGeneratorExt
{
public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap, bool hasIdentityKeyWithValue = false)
{
var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || (p.KeyType == KeyType.Identity && !hasIdentityKeyWithValue))).ToList();
var keys = columns.Where(c => c.KeyType != KeyType.NotAKey).Select(p => $"{generator.GetColumnName(classMap, p, false)}=#{p.Name}");
var nonkeycolumns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey).ToList();
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var tablename = generator.GetTableName(classMap);
var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
var valuesSetters = nonkeycolumns.Select(p => $"{generator.GetColumnName(classMap, p, false)}=#{p.Name}").ToList();
var where = keys.AppendStrings(seperator: " and ");
var sqlbuilder = new StringBuilder();
sqlbuilder.AppendLine($"IF EXISTS (select * from {tablename} WITH (UPDLOCK, HOLDLOCK) WHERE ({where})) ");
sqlbuilder.AppendLine(valuesSetters.Any() ? $"UPDATE {tablename} SET {valuesSetters.AppendStrings()} WHERE ({where}) " : "SELECT 0 ");
sqlbuilder.AppendLine($"ELSE INSERT INTO {tablename} ({columnNames.AppendStrings()}) VALUES ({parameters.AppendStrings()}) ");
return sqlbuilder.ToString();
}
}
Actually I closed my pull request and remove my fork because:
I see some open pull requests created in 2014
I found a way "inject" my code in Dapper.Extensions.
I remind my problem: I want to create more generic queries for Dapper.Extensions. It means I need to have access to mapping cache for entities, SqlGenerator etc. So here is my way. I want to add ability to make INSERT .. UPDATE ON DUPLICATE KEY for MySQL. I created extension method for ISqlGenerator
public static class SqlGeneratorExt
{
public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap)
{
var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity));
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
var valuesSetters = columns.Select(p => string.Format("{0}=VALUES({1})", generator.GetColumnName(classMap, p, false), p.Name));
string sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2}) ON DUPLICATE KEY UPDATE {3}",
generator.GetTableName(classMap),
columnNames.AppendStrings(),
parameters.AppendStrings(),
valuesSetters.AppendStrings());
return sql;
}
}
One more extension method for IDapperImplementor
public static class DapperImplementorExt
{
public static void InsertUpdateOnDuplicateKey<T>(this IDapperImplementor implementor, IDbConnection connection, IEnumerable<T> entities, int? commandTimeout = null) where T : class
{
IClassMapper classMap = implementor.SqlGenerator.Configuration.GetMap<T>();
var properties = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
string emptyGuidString = Guid.Empty.ToString();
foreach (var e in entities)
{
foreach (var column in properties)
{
if (column.KeyType == KeyType.Guid)
{
object value = column.PropertyInfo.GetValue(e, null);
string stringValue = value.ToString();
if (!string.IsNullOrEmpty(stringValue) && stringValue != emptyGuidString)
{
continue;
}
Guid comb = implementor.SqlGenerator.Configuration.GetNextGuid();
column.PropertyInfo.SetValue(e, comb, null);
}
}
}
string sql = implementor.SqlGenerator.InsertUpdateOnDuplicateKey(classMap);
connection.Execute(sql, entities, null, commandTimeout, CommandType.Text);
}
}
Now I can create new class derived from Database class to use my own sql
public class Db : Database
{
private readonly IDapperImplementor _dapperIml;
public Db(IDbConnection connection, ISqlGenerator sqlGenerator) : base(connection, sqlGenerator)
{
_dapperIml = new DapperImplementor(sqlGenerator);
}
public void InsertUpdateOnDuplicateKey<T>(IEnumerable<T> entities, int? commandTimeout) where T : class
{
_dapperIml.InsertUpdateOnDuplicateKey(Connection, entities, commandTimeout);
}
}
Yeah, it's required to create another DapperImplementor instance because DapperImplementor instance from base class is private :(. So now I can use my Db class to call my own generic sql queries and native queries from Dapper.Extension. Examples of usage Database class instead of IDbConnection extensions can be found here.

Insert Multiple Values and Return Multiple Values

I've just started using Dapper and I've run into the following problem.
I want to insert a bunch of records, and return the inserted records alongside the auto-incremented id.
Using Postgres, I want to run the equivalent of this query:
INSERT INTO players (name)
VALUES ('Player1'), ('Player2'), ('Player3'), ('Player4'), ('Player5')
RETURNING id, name;
Using Dapper to run this query on a list of players and serialise back into a list of players (with the ids) I thought I could do this:
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
}
var players = new List<Player> { new Player { Name = "Player1" }, new Player { Name = "Player2" }, new Player { Name = "Player3" }, new Player { Name = "Player4" }, new Player { Name = "Player5" }}
connection.Query<Player>("INSERT INTO players (name) VALUES (#Name) \r\n" +
"RETURNING id, name, tag;",
players);
This throws the following error (it's a list of players each with a name):
Parameter '#Name' referenced in SQL but not found in parameter list
I believe that Query() may not support lists of parameters, so I tried connection.Execute() instead. Execute works, but obviously it doesn't return back the inserted players with their Ids.
It is worth noting that I can do an INSERT and RETURNING like this when I insert only one value.
Does anyone know how I can do INSERT and RETURNING for multiple values like this with Dapper?
Update
I have this (somewhat dirty) solution:
var sb = new StringBuilder();
sb.Append("INSERT INTO players (name) VALUES \r\n");
var parameters = new ExpandoObject() as IDictionary<string, object>;
var values = new List<string>();
for (int i = 0; i < players.Count; i++)
{
var p = players[i];
values.Add($"(#Name{i})");
parameters[$"Name{i}"] = p.Name;
}
sb.Append(string.Join(", \r\n", values));
sb.Append(" \r\nRETURNING id, name, tag;");
// parameters = { Name1 = "Player1", Name2 = "Player2, ... etc}
var ret = connection.Query<Player>(sb.ToString(), parameters);
So building an ExpandoObject with properties from my Players and then passing that into Dapper Query(). It works, but it seems pretty dirty. Any suggestions on how to improve this?
Firstly, it should be noted that passing a List<Player> to the Execute method as the outermost parameter is essentially the same as:
foreach(var player in players)
connection.Execute(
"INSERT INTO players (name) VALUES (#Name) \r\n" +
"RETURNING id, name, tag;", player);
Dapper just unrolls it for you (unless it is a very specific async scenario where it can pipeline the commands). Dapper does support list-parameter expansion, but this is for leaf-level values, and was constructed for in (...) usage, so the syntax would not come out quite as you want; as an example:
DateTime dateStart = ...
int[] custIds = ...
var orders = conn.Query<Order>(#"
select * from Order
where OrderDate >= #dateStart and CustomerId in #custIds",
new { dateStart, custIds }).AsList();
which becomes the SQL:
select * from Order
where OrderDate >= #dateStart and CustomerId in (#custIds0, #custIds1, ...)
(depending on the number of items in the array)
Your expected usage is one that has been suggested and discussed quite a bit recently; at the current time it isn't supported - the loop unrolling only works for Execute, however, it is looking increasingly likely that we will add something here. The tricky bit is in deciding what the correct behavior is, and whether it is expected that this would essentially concatenate the results of multiple separate operations.
However; to do what you want via LINQ:
var results = players.SelectMany(
player => connection.Query<Player>("...", player)).AsList();
This is the same "unroll the loop and concatenate the results" behavior, except it should work.

Dapper parameters not working

I'm trying to use the Dapper orm with the following simple query:
var sqlString = new StringBuilder();
sqlString.Append("select a.acct AccountNumber,");
sqlString.Append(" b.first_name FirstName,");
sqlString.Append(" b.last_name LastName,");
sqlString.Append(" a.rr RrNumber,");
sqlString.Append(" c.addr1 AddressLine1,");
sqlString.Append(" c.addr2 AddressLine2,");
sqlString.Append(" c.addr3 AddressLine3,");
sqlString.Append(" c.addr4 AddressLine4,");
sqlString.Append(" c.addr5 AddressLine5,");
sqlString.Append(" c.addr6 AddressLine6,");
sqlString.Append(" c.addr7 AddressLine7,");
sqlString.Append(" c.addr8 AddressLine8 ");
sqlString.Append("from (pub.mfclac as a left join pub.mfcl as b on a.client=b.client) ");
sqlString.Append("left join pub.mfclad as c on a.client=c.client ");
sqlString.Append("where a.acct = '#ZYX'");
var connection = new OdbcConnection(_connectionString);
var result = connection.Query(sqlString.ToString(),
new
{
ZYX = accountNumber
});
However when I execute this with an accountNumber known to exist, dapper returns nothing. So I tried to remove the quotes to verify that the parameter is in fact being replaced with the account number, however the error being returned from the server indicates a syntax error around "#ZYX". Which means dapper is not replacing the parameter with it's given value. Any ideas why this is happening? From the limited documentation out there, this should 'just work'.
Edit1
Couldn't get this to work. Using string.format to insert the parameter as a work around.
There are two issues here; firstly (although you note this in your question) where a.acct = '#ZYX', under SQL rules, does not make use of any parameter - it looks to match the literal string that happens to include an # sign. For SQL-Server (see note below), the correct usage would be where a.acct = #ZYX.
However! Since you are use OdbcConnection, named parameters do not apply. If you are actually connecting to something like SQL-Server, I would strongly recommend using the pure ADO.NET clients, which have better features and performance than ODBC. However, if ODBC is your only option: it does not use named parameters. Until a few days ago, this would have represented a major problem, but as per Passing query parameters in Dapper using OleDb, the code (but not yet the NuGet package) now supports ODBC. If you build from source (or wait for the next release), you should be able to use:
...
where a.acct = ?
in your command, and:
var result = connection.Query(sqlString.ToString(),
new {
anythingYouLike = accountNumber
});
Note that the name (anythingYouLike) is not used by ODBC, so can be... anything you like. In a more complex scenario, for example:
.Execute(sql, new { id = 123, name = "abc", when = DateTime.Now });
dapper uses some knowledge of how anonymous types are implemented to understand the original order of the values, so that they are added to the command in the correct sequence (id, name, when).
One final observation:
Which means dapper is not replacing the parameter with it's given value.
Dapper never replaces parameters with their given value. That is simply not the correct way to parameterize sql: the parameters are usually sent separately, ensuring:
there is no SQL injection risk
maximum query plan re-use
no issues of formatting
Note that some ADO.NET / ODBC providers could theoretically choose to implement things internally via replacement - but that is separate to dapper.
I landed here from dublicate question: Dapper must declare the scalar variable
Error: Must declare the scalar variable "#Name".
I created queries dynamically with this piece of code:
public static bool Insert<T>(T entity)
{
var tableName = entity.GetType().CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(TableAttribute))?.ConstructorArguments?.FirstOrDefault().Value as string;
if (string.IsNullOrEmpty(tableName))
throw new Exception($"Cannot save {entity.GetType().Name}. Database models should have [Table(\"tablename\")] attribute.");
DBSchema.TryGetValue(tableName.ToLower(), out var fields);
using (var con = new SqlConnection(ConnectionString))
{
con.Open();
var sql = $"INSERT INTO [{tableName}] (";
foreach (var field in fields.Where(x => x != "id"))
{
sql += $"[{field}]"+",";
}
sql = sql.TrimEnd(',');
sql += ")";
sql += " VALUES (";
foreach (var field in fields.Where(x => x != "id"))
{
sql += "#"+field + ",";
}
sql = sql.TrimEnd(',');
sql += ")";
var affectedRows = con.Execute(sql, entity);
return affectedRows > 0;
}
}
And I got the same error when my models was like this:
[Table("Users")]
public class User
{
public string Name;
public string Age;
}
I changed them to this:
[Table("Users")]
public class User
{
public string Name { get; set; }
public string Age { get; set; }
}
And it solved the problem for me.

Regex Replace to assist Orderby in LINQ

I'm using LINQ to SQL to pull records from a database, sort them by a string field, then perform some other work on them. Unfortunately the Name field that I'm sorting by comes out of the database like this
Name
ADAPT1
ADAPT10
ADAPT11
...
ADAPT2
ADAPT3
I'd like to sort the Name field in numerical order. Right now I'm using the Regex object to replace "ADAPT1" with "ADAPT01", etc. I then sort the records again using another LINQ query. The code I have for this looks like
var adaptationsUnsorted = from aun in dbContext.Adaptations
where aun.EventID == iep.EventID
select new Adaptation
{
StudentID = aun.StudentID,
EventID = aun.EventID,
Name = Regex.Replace(aun.Name,
#"ADAPT([0-9])$", #"ADAPT0$1"),
Value = aun.Value
};
var adaptationsSorted = from ast in adaptationsUnsorted
orderby ast.Name
select ast;
foreach(Adaptation adaptation in adaptationsSorted)
{
// do real work
}
The problem I have is that the foreach loop throws the exception
System.NotSupportedException was unhandled
Message="Method 'System.String Replace(System.String, System.String,
System.String)' has no supported translation to SQL."
Source="System.Data.Linq"
I'm also wondering if there's a cleaner way to do this with just one LINQ query. Any suggestions would be appreciated.
Force the hydration of the elements by enumerating the query (call ToList). From that point on, your operations will be against in-memory objects and those operations will not be translated into SQL.
List<Adaptation> result =
dbContext.Adaptation
.Where(aun => aun.EventID = iep.EventID)
.ToList();
result.ForEach(aun =>
aun.Name = Regex.Replace(aun.Name,
#"ADAPT([0-9])$", #"ADAPT0$1")
);
result = result.OrderBy(aun => aun.Name).ToList();
Implement a IComparer<string> with your logic:
var adaptationsUnsorted = from aun in dbContext.Adaptations
where aun.EventID == iep.EventID
select new Adaptation
{
StudentID = aun.StudentID,
EventID = aun.EventID,
Name = aun.Name,
Value = aun.Value
};
var adaptationsSorted = adaptationsUnsorted.ToList<Adaptation>().OrderBy(a => a.Name, new AdaptationComparer ());
foreach (Adaptation adaptation in adaptationsSorted)
{
// do real work
}
public class AdaptationComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string x1 = Regex.Replace(x, #"ADAPT([0-9])$", #"ADAPT0$1");
string y1 = Regex.Replace(y, #"ADAPT([0-9])$", #"ADAPT0$1");
return Comparer<string>.Default.Compare(x1, y1);
}
}
I didn't test this code but it should do the job.
I wonder if you can add a calculated+persisted+indexed field to the database, that does this for you. It would be fairly trivial to write a UDF that gets the value as an integer (just using string values), but then you can sort on this column at the database. This would allow you to use Skip and Take effectively, rather than constantly fetching all the data to the .NET code (which simply doesn't scale).

Categories