NHibernate HQL Generator Caching Expression - sql

I have created the following NHibernate HQL generator:
public class ContainsGenerator : BaseHqlGeneratorForMethod {
public ContainsGenerator() {
SupportedMethods = new[] {
ReflectionHelper.GetMethodDefinition(() =>
MyExtensions.Contains(null, null))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject,
ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor) {
var exp = FormatExpression((string)((ConstantExpression)arguments[1]).Value);
return treeBuilder.BooleanMethodCall("CONTAINS", new[] {
visitor.Visit(arguments[0]).AsExpression(),
treeBuilder.Constant(exp)
});
}
private string FormatExpression(string exp) {
exp = exp.Replace("'", "''");
exp = exp.Replace("\"", "");
exp = (char)34 + Regex.Replace(exp,
"(AND NOT|AND|OR NOT|OR) ",
(char)34 + " $1 " + (char)34, RegexOptions.IgnoreCase)
+ (char)34;
return exp;
}
}
This is used to do Full-Text searching to speed up searching over large tables.
The only difference to this and previous generators I've built in the past is that I call FormatExpression to convert the search expression to the correct format that SQL Server understands. However this seems to be the problem because although it works the first time I fire the query, subsequent searches produces the same query and the second argument passed into CONTAINS never changes. For example If I say:
var products = session.Query<Product>().Where(p => p.Name.Contains("Test 1")).ToList();
It will produce the following query:
select product0_.Id as Id2_,
product0_.Name as Name2_, from [dbo].Products product0_ where CONTAINS(product0_.Name, '"Test 1"')
Now if I say:
var products = session.Query<Product>().Where(p => p.Name.Contains("Test 2")).ToList();
It produces exactly the same query.
I'd appreciate it if someone could show me the correct way to do this. Thanks

It is a known bug in NHibernate: see https://nhibernate.jira.com/browse/NH-2658.

Related

Get and manipulate data from elasticsearch

I am new to elasticsearch so I will need some help. Unfortunately, I didnt found the answer in other topics here on SO.
I have some .net core application which I inherited and now there is a need to implement some changes.
I already have a method of getting data from elasticsearch, but after getting them, I am not sure how to change it and use it in application.
To be precise, I need to parse first and last name and to remove special characters, specific serbian latin letters like "šđžčć" etc... I already have a method for this parsing written but not sure how to call it...
So, my question is can I and how can I do this?
What I have now is the following:
var result = await _elasticClient.SearchAsync<CachedUserEntity>(
s =>
s.Index(_aliasName)
.Query(q => andQuery));
CachedUserEntity, among others, contains property about FirstName and LastName.
Inside results.Documents, I am getting the data about FirstName and LastName from elasticsearch, but I am not sure how to access it in order to update it via aformentioned NameParser ...
Sorry if the question is too easy, not to say stupid :)
I wont use updateByQuery here, for some reasons. I would scroll on documents (i use matchAll on my exemple, you obviously need to replace it with your query), or, if you dont know how to identify documents to update, only update usefull documents in UpdateManyWithIndex/UpdateManyPartial function.
For performance, we have to update severals documents at once, so we use bulk/updateMany function.
You can use both solution, the classic update, or the second (partial update) with an object containing the targeteds fields.
On server sides, both solutions will have the same cost / performance.
var searchResponse = Client.Search<CachedUserEntity>(s => s
.Query(q => q
MatchAll()
)
.Scroll("10s")
);
while (searchResponse.Documents.Any())
{
List<CachedUserEntity> NewSearchResponse = RemoveChar(searchResponse);
UpdateManyWithIndex<CachedUserEntity>(NewSearchResponse, _aliasName);
searchResponse = Client.Scroll<Project>("2h", searchResponse.ScrollId);
}
public void UpdateManyWithIndex<C>(List<C> obj, string index) where C : class {
var bulkResponse = Client.Bulk(b => b
.Index(index).Refresh(Elasticsearch.Net.Refresh.WaitFor) // explicitly provide index name
.UpdateMany<C>(obj, (bu, d) => bu.Doc(d)));
}
Or, using partial update object
Note: in this case Indix is already set on my client (add .index if needed)
var searchResponse = Client.Search<CachedUserEntity>(s => s
.Query(q => q
MatchAll()
)
.Scroll("2h")
);
while (searchResponse.Documents.Any())
{
List<object> listPocoPartialObj = GetPocoPartialObjList(searchResponse);
UpdateManyPartial(listPocoPartialObj);
searchResponse = Client.Scroll<Project>("2h", searchResponse.ScrollId);
}
private List<object> GetPocoPartialObjList(List<CachedUserEntity> cachedList) {
List<object> listPoco = new List<object>();
//note if you dont have cachedList.Id, take a look at result.source, comments if needed
foreach (var eltCached in cachedList) {
listPoco.Add( new object() { Id = cachedList.Id, FirstName = YOURFIELDWITHOUTSPECIALCHAR, LastName = YOURSECONDFIELDWITHOUTSPECIALCHAR});
}
return listPoco;
}
public bool UpdateManyPartial(List<object> partialObj)
{
var bulkResponse = Client.Bulk(b => b
.Refresh(Elasticsearch.Net.Refresh.WaitFor)
.UpdateMany(partialObj, (bu, d) => bu.Doc(d))
);
if (!bulkResponse.IsValid)
{
GetErrorMsgs(bulkResponse);
}
return (bulkResponse?.IsValid == true);
}

How to build SELECT * WHERE using collection of conditions

I want to build a SELECT statement using a list of conditions that come from the query string of a REST api. I wrote this function, but maybe it is vulnerable to SQL injection. Can someone tell me if this is vulnerable how to fix it? Perhaps I should use some kind of SQLBuilder package? or is there a way to do it with just dotNet. I'm using dotNet 4.6.1
string BuildSelect(NameValueCollection query)
{
var result = "SELECT * FROM MYTABLE";
if (query.Count == 0) return result;
var logic = " WHERE ";
foreach (string key in query)
foreach (string v in query.GetValues(key))
{
result += logic + key + " = " + v;
logic = " AND ";
}
return result;
}
Yes it is vulnerable to SQL injection attack. You could build your query to use parameters instead (you are simply using an = check only).
Since you know the tablename, that means you also know what the columns (keys) can be. Thus, you could loop your columns, if the collection has that key then add it to the where as a parameterized statement BUT value part is NOT passed as a string, you parse it to the type it should be (or let the backend do the conversion and get error if cannot be converted). In pseudocode:
List<string> clauses = new List<string>();
var result = "SELECT * FROM MYTABLE";
foreach( var col in myTable.Columns )
{
if (query.ContainsKey(col.Name))
{
clauses.Add( $"{col.Name} = #{col.Name}";
string v = query[col.Name];
command.Parameters.Add( $"#{col.Name}", col.Type).Value = typeParse(v);
}
}
if (clauses.Any())
{
result += " WHERE " + string.Join( " AND ", clauses );
}
return result;
HTH

Write Round in NHibernate criteria

I need to write this in NHibernate Criteria as a projection:
The subAlias is _not_ the root alias, so {alias} cannot replace the correct sql alias, and my problem is that other parts of the query makes the subAlias vary in the generated sql
ROUND(alias.Property / parameterValueFromMethodParameter + ", 0)
* parameterValueFromMethodParameter2 AS SQLAlias
This is how far (off) I got:
.Add(Projections.SqlFunction(new VarArgsSQLFunction("(", "/", ")")
, NHibernateUtil.Int32
, Projections.SqlFunction("round"
, NHibernateUtil.Decimal
, Projections.Property("subAlias.Property"))), "SQLAlias"))
This produces the following SQL code:
ROUND( subAlias3(4).Property
)AS y1_
Does anyone have experience with projections like this?
I found this patch i hibernate, but seems like it was not implemented.
If I understand your example properly, the most easy solution would be to use SQL projection:
// the parameterValueFromMethodParameter
// and parameterValueFromMethodParameter2
var computationParams = new object[] {2, 4}; // just an example numbers
// SQL To be generated
// see that here we work with COLUMN name, not property
var sqlSnippet = " ( ROUND({{alias}}.ColumnName / {0}, 0) * {1} ) AS computed ";
// put that all together
var projectSql = string.Format(sqlSnippet, computationParams);
// IProjection
var projection = Projections.SqlProjection(projectSql, new string[0], new IType[0]);
// add it to SELECT clause
criteria.SetProjection(Projections.ProjectionList()
.Add(projection)
...
);
That should work...
I Solved it by writing my own SQL IProjection. With a litte help from this example.
public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
{
string replacedString = Regex.Replace(
this.sql,
#"{([a-zA-Z_]((\.)?[a-zA-Z0-9_])*)}",
m =>
{
ICriteria critter = criteria.GetCriteriaByAlias(m.Groups[1].Value);
if (critter != null)
{
return criteriaQuery.GetSQLAlias(critter);
}
return m.Groups[0].Value;
});
return new SqlString(replacedString);
}
So now I can do (In a SqlGroupProjection):
ROUND({subAlias}.XCoord / " + aggregationSize + ", 0) * " + aggregationSize + " AS SQLAlias
There are some other attempts on extending (N)Hibernate to handle this alias in raw SQL problem:
Expression.Sql should support aliases other than {alias}
Support for referencing non-root entities in Criteria SQL expressions

How to get rid of StringTemplate warning "\n in string"

I'm using StringTemplate 4 to generate some Java source files.
The templates are really simple, e.g.:
QueryHandler(method) ::="
public class Obtenir<method.name>Handler extends QueryHandler\<List\<<method.name>Db>> implements IQueryHandler\<List\<<method.name>>>{
private IQuery\<List\<<method.name>Db>> query;
private <method.name>Converter converter;
#Inject
public Obtenir<method.name>Handler(IQuery\<List\<<method.name>Db>> query, <method.name>Converter converter, IStatisticsCollecter theStatsCollecter){
super(theStatsCollecter);
if(query == null){
throw new IllegalArgumentException(\"The query argument cannot be null\");
}
if(converter == null){
throw new IllegalArgumentException(\"Illegal argument for converter(null)\");
}
this.query = query;
this.converter = converter;
}
public List\<<method.name>> handle(Object... params) throws JdbcException {
final String method = \"obtenir<method.name>\";
DaoQueryStatusCallable status = new DaoQueryStatusCallable();
List\<<method.name>Db> result = invoke(query, status, method);
return converter.convert(result);
}
}
"
The code is even simpler:
STGroup group = new STGroupFile("src/main/resources/QueryHandler.stg");
ST wsTemplate = group.getInstanceOf("QueryHandler");
wsTemplate.add("method", m);
System.out.println(wsTemplate.render());
The template lines are separated by Unix EOLs (\n).
When I execute the code, StringTemplate is emitting a warning "QueryHandler.stg 1:25: \n in string".
The result is correct, but I'd still like to get rid of this message.
Anybody ever had this problem and knows how to solve it?
t() ::= "..." is meant only for single lines. Please use
t() ::= <<
...
>>
to get multi-line templates.
Ter

Does Dapper support the like operator?

Using Dapper-dot-net...
The following yields no results in the data object:
var data = conn.Query(#"
select top 25
Term as Label,
Type,
ID
from SearchTerms
WHERE Term like '%#T%'",
new { T = (string)term });
However, when I just use a regular String Format like:
string QueryString = String.Format("select top 25 Term as Label, Type, ID from SearchTerms WHERE Term like '%{0}%'", term);
var data = conn.Query(QueryString);
I get 25 rows back in the collection. Is Dapper not correctly parsing the end of the parameter #T?
Try:
term = "whateverterm";
var encodeForLike = term => term.Replace("[", "[[]").Replace("%", "[%]");
string term = "%" + encodeForLike(term) + "%";
var data = conn.Query(#"
select top 25
Term as Label,
Type,
ID
from SearchTerms
WHERE Term like #term",
new { term });
There is nothing special about like operators, you never want your params inside string literals, they will not work, instead they will be interpreted as a string.
note
The hard-coded example in your second snippet is strongly discouraged, besides being a huge problem with sql injection, it can cause dapper to leak.
caveat
Any like match that is leading with a wildcard is not SARGable, which means it is slow and will require an index scan.
Yes it does. This simple solution has worked for me everytime:
db.Query<Remitente>("SELECT *
FROM Remitentes
WHERE Nombre LIKE #n", new { n = "%" + nombre + "%" })
.ToList();
Best way to use this to add concat function in query as it save in sql injecting as well, but concat function is only support above than sql 2012
string query = "SELECT * from country WHERE Name LIKE CONCAT('%',#name,'%');"
var results = connection.query<country>(query, new {name});
The answer from Sam wasn't working for me so after some testing I came up with using the SQLite CONCAT equivalent which seems to work:
string sql = "SELECT * FROM myTable WHERE Name LIKE '%' || #NAME || '%'";
var data = IEnumerable data = conn.Query(sql, new { NAME = Name });
Just to digress on Sam's answer, here is how I created two helper methods to make searches a bit easier using the LIKE operator.
First, creating a method for generating a parameterized query, this method uses dynamic: , but creating a strongly typed generic method should be more desired in many cases where you want static typing instead of dynamic.
public static dynamic ParameterizedQuery(this IDbConnection connection, string sql, Dictionary<string, object> parametersDictionary)
{
if (string.IsNullOrEmpty(sql))
{
return null;
}
string missingParameters = string.Empty;
foreach (var item in parametersDictionary)
{
if (!sql.Contains(item.Key))
{
missingParameters += $"Missing parameter: {item.Key}";
}
}
if (!string.IsNullOrEmpty(missingParameters))
{
throw new ArgumentException($"Parameterized query failed. {missingParameters}");
}
var parameters = new DynamicParameters(parametersDictionary);
return connection.Query(sql, parameters);
}
Then adding a method to create a Like search term that will work with Dapper.
public static string Like(string searchTerm)
{
if (string.IsNullOrEmpty(searchTerm))
{
return null;
}
Func<string, string> encodeForLike = searchTerm => searchTerm.Replace("[", "[[]").Replace("%", "[%]");
return $"%{encodeForLike(searchTerm)}%";
}
Example usage:
var sql = $"select * from products where ProductName like #ProdName";
var herringsInNorthwindDb = connection.ParameterizedQuery(sql, new Dictionary<string, object> { { "#ProdName", Like("sild") } });
foreach (var herring in herringsInNorthwindDb)
{
Console.WriteLine($"{herring.ProductName}");
}
And we get our sample data from Northwind DB:
I like this approach, since we get helper extension methods to do repetitive work.
My solution simple to this problem :
parameter.Add("#nomeCliente", dfNomeCliPesquisa.Text.ToUpper());
query = "SELECT * FROM cadastrocliente WHERE upper(nome) LIKE " + "'%" + dfNomeCliPesquisa.Text.ToUpper() + "%'";