I am trying to create a projection which will filter results from the database using the Levenshtein search distance calculation. To create this I open a session to the DB and then use CreateCriteria to query the database:
...
return session.CreateCriteria<Contact>()
.Add(Expression.Le(Levenshtein("FullName", "Bob"), 5)
...
Created a small helper method to return a new instance of the projection class
public static LevenshteinProjection Levenshtein(string propertyName, string searchValue)
{
return new LevenshteinProjection(propertyName, searchValue);
}
Essentially everything works fine when creating the string to compile the text but when I look at the SQL that is produced the value I want to be less than or equal to is a ?!
... {rest of sql select} WHERE levenshtein(this_.full_name, 'Bob') <= ?
Why is it adding the ? I've set the return type to
public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
{
return new IType[] { NHibernateUtil.Int32 };
}
Thanks
The ? represents the parameter placeholder.
NHibernate generates a 'parameterized query'. Instead of creating a query with the parameter-value hardcoded, it generates a query that contains a parameter.
The parameter will be assigned with the value that you provide.
This enables the DBMS to cache query execution plans.
Related
I have a case where I need to send tens of thousands of ids to the graphql server in the filtering query.
The query now generated by the HT is something like this:
_dbContext.
Forms
.Where(c=>staticLiistOfIds.Contains(c.Id))
.Select(c=>new {C.Name,C.Age});
I have two problems with this:
slow performance
SQL Server Limit I guess is around 32K
I have found a Nuget library to convert this static list to a temp table,so now I want to override the HT middle to rewrite the above query generated to the following:
_dbContext.
Forms
.Where(c=>_dbContext..AsQueryableValues(staticLiistOfIds).Contains(c.Id))
.Select(c=>new {C.Name,C.Age});
This will create a temp table for this static list of ids so I will be able to solve the above two problems that I have.
So since i didn't get answers, I had to ask from the Slack of HotChocolate's Team and hopefully, they provided me with the documentation extending-filtering/extending-iqueryable:
in case the link was broken, here is
Extending IQueryable The default filtering implementation uses
IQueryable under the hood. You can customize the translation of
queries by registering handlers on the QueryableFilterProvider.
The following example creates a StringOperationHandler that supports
case-insensitive filtering:
// The QueryableStringOperationHandler already has an implemenation of CanHandle
// It checks if the field is declared in a string operation type and also checks if
// the operation of this field uses the `Operation` specified in the override property further
// below
public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHandler
{
// For creating a expression tree we need the `MethodInfo` of the `ToLower` method of string
private static readonly MethodInfo _toLower = typeof(string)
.GetMethods()
.Single(
x => x.Name == nameof(string.ToLower) &&
x.GetParameters().Length == 0);
// This is used to match the handler to all `eq` fields
protected override int Operation => DefaultFilterOperations.Equals;
public override Expression HandleOperation(
QueryableFilterContext context,
IFilterOperationField field,
IValueNode value,
object parsedValue)
{
// We get the instance of the context. This is the expression path to the propert
// e.g. ~> y.Street
Expression property = context.GetInstance();
// the parsed value is what was specified in the query
// e.g. ~> eq: "221B Baker Street"
if (parsedValue is string str)
{
// Creates and returnes the operation
// e.g. ~> y.Street.ToLower() == "221b baker street"
return Expression.Equal(
Expression.Call(property, _toLower),
Expression.Constant(str.ToLower()));
}
// Something went wrong 😱
throw new InvalidOperationException();
}
}
This operation handler can be registered on the convention:
public class CustomFilteringConvention : FilterConvention
{
protected override void Configure(IFilterConventionDescriptor descriptor)
{
descriptor.AddDefaults();
descriptor.Provider(
new QueryableFilterProvider(
x => x
.AddDefaultFieldHandlers()
.AddFieldHandler<QueryableStringInvariantEqualsHandler>()));
}
}
// and then
services.AddGraphQLServer()
.AddFiltering<CustomFilteringConvention>();
To make this registration easier, Hot Chocolate also supports
convention and provider extensions. Instead of creating a custom
FilterConvention, you can also do the following:
services
.AddGraphQLServer()
.AddFiltering()
.AddConvention<IFilterConvention>(
new FilterConventionExtension(
x => x.AddProviderExtension(
new QueryableFilterProviderExtension(
y => y.AddFieldHandler<QueryableStringInvariantEqualsHandler>()))));
but I was suggested that doing this way(sending up to 100k list of string ids to the graphQL server) is not a good approach. so I decided to take another approach by writing a custom simple dynamic LINQ generates.
Thanks.
I'm building SQL query engine that should take SQL Query as a string in the following format
from (Collection) select (fields) where (conditions)and run it over my Data class (which consists of List fields like List<Person>) and return the result of query
I've already created classes etc. now I just need to run the queries.
Query consists of Source string, ConditionsSet object which have the list of conditions, and Fields string collection which consists of names of fields that we want to display if the record match the conditions.
Let's jump to the code.
public void RunQuery(Data data, Query query)
{
var table = data.GetType().GetField(query.Source).GetValue(data); //Source object
// var output = from entry in table where QueryEngine.IsMatching(entry, query.ConditionsSet) select entry;
// Is something like this is possible? How to approach/do that? Am I forced to not use linq?
// The compiler tells that I cant use Linq because it cant find GetEnumerator in the table object
}
private bool IsMatching(object entry, ConditionsSet set)
{
foreach (Condition c in set.Conditions) // For example we assume the operator is == equality and every condition is separated by AND keyword
if (entry.GetType().GetField(c.Field).GetValue(entry).ToString() != c.Value) //c.Value is string
return false;
return true;
}
How should I approach that? Is LINQ unavailable for me?
I have two tables Studies and Series. Series are FK'd back to Studies so one Study contains a variable number of Series.
Each Series item has a Deleted column indicating it has been logically deleted from the database.
I am trying to implement a Deleted property in the Study class that returns true only if all the contained Series are deleted.
I am using O/R Designer generated classes, so I added the following to the user modifiable partial class for the Study type:
public bool Deleted
{
get
{
var nonDeletedSeries = from s in Series
where !s.Deleted
select s;
return nonDeletedSeries.Count() == 0;
}
set
{
foreach (var series in Series)
{
series.Deleted = value;
}
}
}
This gives an exception "The member 'PiccoloDatabase.Study.Deleted' has no supported translation to SQL." when this simple query is executed that invokes get:
IQueryable<Study> dataQuery = dbCtxt.Studies;
dataQuery = dataQuery.Where((s) => !s.Deleted);
foreach (var study in dataQuery)
{
...
}
Based on this http://www.foliotek.com/devblog/using-custom-properties-inside-linq-to-sql-queries/, I tried the following approach:
static Expression<Func<Study, bool>> DeletedExpr = t => false;
public bool Deleted
{
get
{
var nameFunc = DeletedExpr.Compile();
return nameFunc(this);
}
set
{ ... same as before
}
}
I get the same exception when a query is run that there is no supported translation to SQL. (
The logic of the lambda expression is irrelevant yet - just trying to get past the exception.)
Am I missing some fundamental property or something to allow translation to SQL? I've read most of the posts on SO about this exception, but nothing seems to fit my case exactly.
I believe the point of LINQ-to-SQL is that your entities are mapped for you and must have correlations in the database. It appears that you are trying to mix the LINQ-to-Objects and LINQ-to-SQL.
If the Series table has a Deleted field in the database, and the Study table does not but you would like to translate logical Study.Deleted into SQL, then extension would be a way to go.
public static class StudyExtensions
{
public static IQueryable<study> AllDeleted(this IQueryable<study> studies)
{
return studies.Where(study => !study.series.Any(series => !series.deleted));
}
}
class Program
{
public static void Main()
{
DBDataContext db = new DBDataContext();
db.Log = Console.Out;
var deletedStudies =
from study in db.studies.AllDeleted()
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}
}
}
This maps your "deleted study" expression into SQL:
SELECT t0.study_id, t0.name
FROM study AS t0
WHERE NOT EXISTS(
SELECT NULL AS EMPTY
FROM series AS t1
WHERE (NOT (t1.deleted = 1)) AND (t1.fk_study_id = t0.study_id)
)
Alternatively you could build actual expressions and inject them into your query, but that is an overkill.
If however, neither Series nor Study has the Deleted field in the database, but only in memory, then you need to first convert your query to IEnumerable and only then access the Deleted property. However doing so would transfer records into memory before applying the predicate and could potentially be expensive. I.e.
var deletedStudies =
from study in db.studies.ToList()
where study.Deleted
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}
When you make your query, you will want to use the statically defined Expression, not the property.
Effectively, instead of:
dataQuery = dataQuery.Where((s) => !s.Deleted);
Whenever you are making a Linq to SQL query, you will instead want to use:
dataQuery = dataQuery.Where(DeletedExpr);
Note that this will require that you can see DeletedExpr from dataQuery, so you will either need to move it out of your class, or expose it (i.e. make it public, in which case you would access it via the class definition: Series.DeletedExpr).
Also, an Expression is limited in that it cannot have a function body. So, DeletedExpr might look something like:
public static Expression<Func<Study, bool>> DeletedExpr = s => s.Series.Any(se => se.Deleted);
The property is added simply for convenience, so that you can also use it as a part of your code objects without needing to duplicate the code, i.e.
var s = new Study();
if (s.Deleted)
...
I am trying to create a pageing and sorting object data source that before execution returns all results, then sorts on these results before filtering and then using the take and skip methods with the aim of retrieving just a subset of results from the database (saving on database traffic). this is based on the following article:
http://www.singingeels.com/Blogs/Nullable/2008/03/26/Dynamic_LINQ_OrderBy_using_String_Names.aspx
Now I have managed to get this working even creating lambda expressions to reflect the sort expression returned from the grid even finding out the data type to sort for DateTime and Decimal.
public static string GetReturnType<TInput>(string value)
{
var param = Expression.Parameter(typeof(TInput), "o");
Expression a = Expression.Property(param, "DisplayPriceType");
Expression b = Expression.Property(a, "Name");
Expression converted = Expression.Convert(Expression.Property(param, value), typeof(object));
Expression<Func<TInput, object>> mySortExpression = Expression.Lambda<Func<TInput, object>>(converted, param);
UnaryExpression member = (UnaryExpression)mySortExpression.Body;
return member.Operand.Type.FullName;
}
Now the problem I have is that many of the Queries return joined tables and I would like to sort on fields from the other tables.
So when executing a query you can create a function that will assign the properties from other tables to properties created in the partial class.
public static Account InitAccount(Account account)
{
account.CurrencyName = account.Currency.Name;
account.PriceTypeName = account.DisplayPriceType.Name;
return account;
}
So my question is, is there a way to assign the value from the joined table to the property of the current table partial class? i have tried using.
from a in dc.Accounts
where a.CompanyID == companyID
&& a.Archived == null
select new {
PriceTypeName = a.DisplayPriceType.Name})
but this seems to mess up my SortExpression.
Any help on this would be much appreciated, I do understand that this is complex stuff.
This is a functional programming thing. Mutating Account by doing an assignment is out. New-ing up a new instance of the shape you want is in.
Step 1: declare a class that has the shape of the result you want:
public class QueryResult
{
public int CompanyID {get;set;}
public string CurrencyName {get;set;}
public string PriceTypeName {get;set;}
}
Step 2: project into that class in your query
from ...
where ...
select new QueryResult()
{
CompanyID = a.CompanyID,
CurrencyName = a.Currency.Name,
PriceTypeName = a.PriceType.Name
};
Step 3: Profit! (order by that)
The query generator will use the details of your QueryResult type to generate a select clause with that shape.
If you do something like this in your Repository:
IQueryable<CarClass> GetCars(string condition, params object[] values) {
return db.Cars.Where(condition, values);
}
And you set the condition and values outside of the repository:
string condition = "CarMake == #Make";
object[] values = new string[] { Make = "Ford" };
var result = myRepo.GetCars( condition, values);
How would you be able to sort the result outside of the repository with Dynamic Query?
return View( "myView", result.OrderBy("Price"));
Somehow I am losing the DynamicQuery nature when the data exits from the repository. And yes, I haven't worked out how to return the CarClass type where you would normally do a Select new Carclass { fieldName = m.fieldName, ... }
Dynamic query requires:
the source be IQueryable<T> (so if it is IEnumerable<T> or similar, just call .AsQueryable() on it)
an extra dll to be referenced in the code that wants to perform dynamic query
the appropriate using directives to be in place at the top of the local source file
Check those three, and you should be able to add .Where(condition), .OrderBy(name), etc