using the TSqlParser - tsql

I'm attempting to parse SQL using the TSql100Parser provided by microsoft. Right now I'm having a little trouble using it the way it seems to be intended to be used. Also, the lack of documentation doesn't help. (example: http://msdn.microsoft.com/en-us/library/microsoft.data.schema.scriptdom.sql.tsql100parser.aspx )
When I run a simple SELECT statement through the parser it returns a collection of TSqlStatements which contains a SELECT statement.
Trouble is, the TSqlSelect statement doesn't contain attributes such as a WHERE clause, even though the clause is implemented as a class. http://msdn.microsoft.com/en-us/library/microsoft.data.schema.scriptdom.sql.whereclause.aspx
The parser does recognise the WHERE clause as such, looking at the token stream.
So, my question is, am I using the parser correctly? Right now the token stream seems to be the most useful feature of the parser...
My Test project:
public static void Main(string[] args)
{
var parser = new TSql100Parser(false);
IList<ParseError> Errors;
IScriptFragment result = parser.Parse(
new StringReader("Select col from T1 where 1 = 1 group by 1;" +
"select col2 from T2;" +
"select col1 from tbl1 where id in (select id from tbl);"),
out Errors);
var Script = result as TSqlScript;
foreach (var ts in Script.Batches)
{
Console.WriteLine("new batch");
foreach (var st in ts.Statements)
{
IterateStatement(st);
}
}
}
static void IterateStatement(TSqlStatement statement)
{
Console.WriteLine("New Statement");
if (statement is SelectStatement)
{
PrintStatement(sstmnt);
}
}

Yes, you are using the parser correctly.
As Damien_The_Unbeliever points out, within the SelectStatement there is a QueryExpression property which will be a QuerySpecification object for your third select statement (with the WHERE clause).
This represents the 'real' SELECT bit of the query (whereas the outer SelectStatement object you are looking at has just got the 'WITH' clause (for CTEs), 'FOR' clause (for XML), 'ORDER BY' and other bits)
The QuerySpecification object is the object with the FromClauses, WhereClause, GroupByClause etc.
So you can get to your WHERE Clause by using:
((QuerySpecification)((SelectStatement)statement).QueryExpression).WhereClause
which has a SearchCondition property etc. etc.

Quick glance around would indicate that it contains a QueryExpression, which could be a QuerySpecification, which does have the Where clause attached to it.

if someone lands here and wants to know how to get the whole elements of a select statement the following code explain that:
QuerySpecification spec = (QuerySpecification)(((SelectStatement)st).QueryExpression);
StringBuilder sb = new StringBuilder();
sb.AppendLine("Select Elements");
foreach (var elm in spec.SelectElements)
sb.Append(((Identifier)((Column)((SelectColumn)elm).Expression).Identifiers[0]).Value);
sb.AppendLine();
sb.AppendLine("From Elements");
foreach (var elm in spec.FromClauses)
sb.Append(((SchemaObjectTableSource)elm).SchemaObject.BaseIdentifier.Value);
sb.AppendLine();
sb.AppendLine("Where Elements");
BinaryExpression binaryexp = (BinaryExpression)spec.WhereClause.SearchCondition;
sb.Append("operator is " + binaryexp.BinaryExpressionType);
if (binaryexp.FirstExpression is Column)
sb.Append(" First exp is " + ((Identifier)((Column)binaryexp.FirstExpression).Identifiers[0]).Value);
if (binaryexp.SecondExpression is Literal)
sb.Append(" Second exp is " + ((Literal)binaryexp.SecondExpression).Value);

I had to split a SELECT statement into pieces. My goal was to COUNT how many record a query will return. My first solution was to build a sub query such as
SELECT COUNT(*) FROM (select id, name from T where cat='A' order by id) as QUERY
The problem was that in this case the order clause raises the error "The ORDER BY clause is not valid in views, inline functions, derived tables, sub-queries, and common table expressions, unless TOP or FOR XML is also specified"
So I built a parser that split a SELECT statment into fragments using the TSql100Parser class.
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.ScriptDom;
using System.IO;
...
public class SelectParser
{
public string Parse(string sqlSelect, out string fields, out string from, out string groupby, out string where, out string having, out string orderby)
{
TSql100Parser parser = new TSql100Parser(false);
TextReader rd = new StringReader(sqlSelect);
IList<ParseError> errors;
var fragments = parser.Parse(rd, out errors);
fields = string.Empty;
from = string.Empty;
groupby = string.Empty;
where = string.Empty;
orderby = string.Empty;
having = string.Empty;
if (errors.Count > 0)
{
var retMessage = string.Empty;
foreach (var error in errors)
{
retMessage += error.Identifier + " - " + error.Message + " - position: " + error.Offset + "; ";
}
return retMessage;
}
try
{
// Extract the query assuming it is a SelectStatement
var query = ((fragments as TSqlScript).Batches[0].Statements[0] as SelectStatement).QueryExpression;
// Constructs the From clause with the optional joins
from = (query as QuerySpecification).FromClauses[0].GetString();
// Extract the where clause
where = (query as QuerySpecification).WhereClause.GetString();
// Get the field list
var fieldList = new List<string>();
foreach (var f in (query as QuerySpecification).SelectElements)
fieldList.Add((f as SelectColumn).GetString());
fields = string.Join(", ", fieldList.ToArray());
// Get The group by clause
groupby = (query as QuerySpecification).GroupByClause.GetString();
// Get the having clause of the query
having = (query as QuerySpecification).HavingClause.GetString();
// Get the order by clause
orderby = ((fragments as TSqlScript).Batches[0].Statements[0] as SelectStatement).OrderByClause.GetString();
}
catch (Exception ex)
{
return ex.ToString();
}
return string.Empty;
}
}
public static class Extension
{
/// <summary>
/// Get a string representing the SQL source fragment
/// </summary>
/// <param name="statement">The SQL Statement to get the string from, can be any derived class</param>
/// <returns>The SQL that represents the object</returns>
public static string GetString(this TSqlFragment statement)
{
string s = string.Empty;
if (statement == null) return string.Empty;
for (int i = statement.FirstTokenIndex; i <= statement.LastTokenIndex; i++)
{
s += statement.ScriptTokenStream[i].Text;
}
return s;
}
}
And to use this class simply:
string fields, from, groupby, where, having, orderby;
SelectParser selectParser = new SelectParser();
var retMessage = selectParser.Parse("SELECT * FROM T where cat='A' Order by Id desc",
out fields, out from, out groupby, out where, out having, out orderby);

Related

mvc entity framework select with dynamic where clause

I am doing a asp.net-mvc with entity-framework App.
I have a select instruction, where I need to have a dymanic where condition.
It is a common case where you have a filter composed by a string like "aaaa bbbb cccc". I need to bring all data that contains all the filter string or part of it.
I doing part ot it witt Split function, but it is difficult to get all posible combinations.
I Would rather use a Store Procedure. But the porpose of it, is to use Entity Framework.
As far I did this.
public IEnumerable<UploadSearch> GetUploadsBySearch(string search)
{
IEnumerable<UploadSearch> viewModel = (from uploads in _db.Uploads
.Where(p => p.ProcessState_id == Security.APPROVED && p.Finder.Contains(search))
.OrderByDescending(p => p.UploadDate)
select new UploadSearch
{
User_id = uploads.User_id,
UserName = uploads.Users.Name,
UserLastName = uploads.Users.LastName,
});
And I Add a for instruction to loop throu the string,
string[] param = search.Replace(" "," ").Split(' ');
string _param = "";
int large = param.Length;
for (int i=0;i<large-1;i++)
{
_param +=param[i] ' ' + param[i + 1];
IEnumerable<UploadSearch> _viewModel = (from uploads in _db.Uploads
.Where(p => p.ProcessState_id == Security.APPROVED && p.Finder.Contains(_param))
.OrderByDescending(p => p.UploadDate)
select new UploadSearch
{
User_id = uploads.User_id,
UserName = uploads.Users.Name,
UserLastName = uploads.Users.LastName,
});
viewModel = viewModel.Union(_viewModel);
}
And I use a Union clause.
But There has to be another way to do it.
Any Ideas?
You should use LinqKit, it has a PredicateBuilder class that allows you to dynamically build queries
https://github.com/scottksmith95/LINQKit#predicatebuilder
IQueryable<Product> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.New<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);
}

Build dynamic LINQ queries from a string - Use Reflection?

I have some word templates(maybe thousands). Each template has merge fields which will be filled from database. I don`t like writing separate code for every template and then build the application and deploy it whenever a template is changed or a field on the template is added!
Instead, I'm trying to define all merge fields in a separate xml file and for each field I want to write the "query" which will be called when needed. EX:
mergefield1 will call query "Case.Parties.FirstOrDefault.NameEn"
mergefield2 will call query "Case.CaseNumber"
mergefield3 will call query "Case.Documents.FirstOrDefault.DocumentContent.DocumentType"
Etc,
So, for a particular template I scan its merge fields, and for each merge field I take it`s "query definition" and make that request to database using EntityFramework and LINQ. Ex. it works for these queries: "TimeSlots.FirstOrDefault.StartDateTime" or
"Case.CaseNumber"
This will be an engine which will generate word documents and fill it with merge fields from xml. In addition, it will work for any new template or new merge field.
Now, I have worked a version using reflection.
public string GetColumnValueByObjectByName(Expression<Func<TEntity, bool>> filter = null, string objectName = "", string dllName = "", string objectID = "", string propertyName = "")
{
string objectDllName = objectName + ", " + dllName;
Type type = Type.GetType(objectDllName);
Guid oID = new Guid(objectID);
dynamic Entity = context.Set(type).Find(oID); // get Object by Type and ObjectID
string value = ""; //the value which will be filled with data from database
IEnumerable<string> linqMethods = typeof(System.Linq.Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Select(s => s.Name).ToList(); //get all linq methods and save them as list of strings
if (propertyName.Contains('.'))
{
string[] properies = propertyName.Split('.');
dynamic object1 = Entity;
IEnumerable<dynamic> Child = new List<dynamic>();
for (int i = 0; i < properies.Length; i++)
{
if (i < properies.Length - 1 && linqMethods.Contains(properies[i + 1]))
{
Child = type.GetProperty(properies[i]).GetValue(object1, null);
}
else if (linqMethods.Contains(properies[i]))
{
object1 = Child.Cast<object>().FirstOrDefault(); //for now works only with FirstOrDefault - Later it will be changed to work with ToList or other linq methods
type = object1.GetType();
}
else
{
if (linqMethods.Contains(properies[i]))
{
object1 = type.GetProperty(properies[i + 1]).GetValue(object1, null);
}
else
{
object1 = type.GetProperty(properies[i]).GetValue(object1, null);
}
type = object1.GetType();
}
}
value = object1.ToString(); //.StartDateTime.ToString();
}
return value;
}
I`m not sure if this is the best approach. Does anyone have a better suggestion, or maybe someone has already done something like this?
To shorten it: The idea is to make generic linq queries to database from a string like: "Case.Parties.FirstOrDefault.NameEn".
Your approach is very good. I have no doubt that it already works.
Another approach is using Expression Tree like #Egorikas have suggested.
Disclaimer: I'm the owner of the project Eval-Expression.NET
In short, this library allows you to evaluate almost any C# code at runtime (What you exactly want to do).
I would suggest you use my library instead. To keep the code:
More readable
Easier to support
Add some flexibility
Example
public string GetColumnValueByObjectByName(Expression<Func<TEntity, bool>> filter = null, string objectName = "", string dllName = "", string objectID = "", string propertyName = "")
{
string objectDllName = objectName + ", " + dllName;
Type type = Type.GetType(objectDllName);
Guid oID = new Guid(objectID);
object Entity = context.Set(type).Find(oID); // get Object by Type and ObjectID
var value = Eval.Execute("x." + propertyName, new { x = entity });
return value.ToString();
}
The library also allow you to use dynamic string with IQueryable
Wiki: LINQ-Dynamic

How do I replicate %Dictionary.ClassDefintionQuery's Summary() in SQL?

There is a procedure in %Dictionary.ClassDefinitionQuery which lists a class summary; in Java I call it like this:
public void readClasses(final Path dir)
throws SQLException
{
final String call
= "{ call %Dictionary.ClassDefinitionQuery_Summary() }";
try (
final CallableStatement statement = connection.prepareCall(call);
final ResultSet rs = statement.executeQuery();
) {
String className;
int count = 0;
while (rs.next()) {
// Skip if System is not 0
if (rs.getInt(5) != 0)
continue;
className = rs.getString(1);
// Also skip if the name starts with a %
if (className.charAt(0) == '%')
continue;
//System.out.println(className);
count++;
}
System.out.println("read: " + count);
}
}
In namespace SAMPLES this returns 491 rows.
I try and replicate it with a pure SQL query like this:
private void listClasses(final Path dir)
throws SQLException
{
final String query = "select id, super"
+ " from %Dictionary.ClassDefinition"
+ " where System = '0' and name not like '\\%%' escape '\\'";
try (
final PreparedStatement statement
= connection.prepareStatement(query);
final ResultSet rs = statement.executeQuery();
) {
int count = 0;
while (rs.next()) {
//System.out.println(rs.getString(1) + ';' + rs.getString(2));
count++;
}
System.out.println("list: " + count);
}
}
Yet when running the program I get this:
list: 555
read: 491
Why are the results different?
I have looked at the code of %Dictionary.ClassDefinitionQuery but I don't understand why it gives different results... All I know, if I store the names in sets and compare, is that:
nothing is missing from list that is in read;
most, but not all, classes returned by list which are not in read are CSP pages.
But that's it.
How I can I replicate the behaviour of the summary procedure in SQL?
Different is in one property. %Dictionary.ClassDefinitionQuery_Summary shows only classes with Deployed<>2. So, sql must be such.
select id,super from %Dictionary.ClassDefinition where deployed <> 2
But one more things is, why count may be different is, such sql requests may be compilled to temporary class, for example "%sqlcq.SAMPLES.cls22"

can i use custom lambda method in entity framework?

i have some methods like:
public static string ToOtherFormat (this string inp)
{
// some code to change inp
return inp;
}
and in my select i want to have code like this:
var DetailMembers = db.TB_Members
.Where(x=> x.FName == obj.ToOtherFormat())
.Select( x=> new { name = (x.FName.ToOtherFormat() + " " + x.LName) , x.ActCode });
i try and just have error. is it possible?
thanks!
i receive this error in simple convert to integer
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
with this code
.Where(x => x.MemberID == Convert.ToInt32(Hmemid.Hash_two_Decrypt())
Looks like you are querying against the database. Your current query will get translated into SQL query and since SQL doesn't recognize your function that is why you get error.
You may get the data from the tables using a query without that function and then later do the formatting on the result set.
i found it on use .AsEnumerable() method like:
var DetailMembers = db.TB_Members.AsEnumerable()
.Where(x=> x.FName == obj.ToOtherFormat())
.Select( x=> new { name = (x.FName.ToOtherFormat() + " " + x.LName) , x.ActCode });

FunctionImport in entity framework 4 issue

I'm using entity framework 4.
I have a stored procedure that just updates one value in my table, namely the application state ID. So I created a stored procedure that looks like this:
ALTER PROCEDURE [dbo].[UpdateApplicationState]
(
#ApplicationID INT,
#ApplicationStateID INT
)
AS
BEGIN
UPDATE
[Application]
SET
ApplicationStateID = #ApplicationStateID
WHERE
ApplicationID = #ApplicationID;
END
I created a function import called UpdateApplicationState. I had initially set its return type to null, but then it wasn't created in the context. So I changed its return type to int. Now it was created in the context. Is it wise to return something from my stored procedure?
Here is my method in my ApplicationRepository class:
public void UpdateApplicationState(int applicationID, int applicationStateID)
{
var result = context.UpdateApplicationState(applicationID, applicationStateID);
}
Here is my calling code to this method in my view:
applicationRepository.UpdateApplicationState(id, newApplicationStateID);
When I run it then I get the following error:
The data reader returned by the store
data provider does not have enough
columns for the query requested.
Any idea/advise on what I can do to get this to work?
Thanks
To get POCO to work with function imports that return null, you can customize the .Context.tt file like this.
Find the "Function Imports" named region (the section that starts with region.Begin("Function Imports"); and ends with region.End();) in the .Context.tt file and replace that whole section with the following:
region.Begin("Function Imports");
foreach (EdmFunction edmFunction in container.FunctionImports)
{
var parameters = FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
string paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
var isReturnTypeVoid = edmFunction.ReturnParameter == null;
string returnTypeElement = String.Empty;
if (!isReturnTypeVoid)
returnTypeElement = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage));
#>
<# if (isReturnTypeVoid) { #>
<#=Accessibility.ForMethod(edmFunction)#> void <#=code.Escape(edmFunction)#>(<#=paramList#>)
<# } else { #>
<#=Accessibility.ForMethod(edmFunction)#> ObjectResult<<#=returnTypeElement#>> <#=code.Escape(edmFunction)#>(<#=paramList#>)
<# } #>
{
<#
foreach (var parameter in parameters)
{
if (!parameter.NeedsLocalVariable)
{
continue;
}
#>
ObjectParameter <#=parameter.LocalVariableName#>;
if (<#=parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"#>)
{
<#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", <#=parameter.FunctionParameterName#>);
}
else
{
<#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", typeof(<#=parameter.RawClrTypeName#>));
}
<#
}
#>
<# if (isReturnTypeVoid) { #>
base.ExecuteFunction("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))#>);
<# } else { #>
return base.ExecuteFunction<<#=returnTypeElement#>>("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))#>);
<# } #>
}
<#
}
region.End();
What I'm doing here is instead of ignoring all function imports that return null, I'm creating a method that returns null. I hope this is helpful.
It is because you do not actually returning anything from your stored procedure. Add a line like below to your SP (SELECT ##ROWCOUNT), and it will be executing properly.
BEGIN
...
SELECT ##ROWCOUNT
END
While this solution will address your issue and actually returns the number of effected rows by your SP, I am not clear on why this is an issue for you:
I had initially set its return type to null, but then it wasn't created in the context.
When doing a Function Import, you can select "None" as return type and it will generate a new method on your ObjectContext with a return type of int. This method basically executes a stored procedure that is defined in the data source; discards any results returned from the function; and returns the number of rows affected by the execution.
EDIT: Why a Function without return value is ignored in a POCO Scenario:
Drilling into ObjectContext T4 template file coming with ADO.NET C# POCO Entity Generator reveals why you cannot see your Function in your ObjectContext class: Simply it's ignored! They escape to the next iteration in the foreach loop that generates the functions.
The workaround for this is to change the T4 template to actually generate a method for Functions without return type or just returning something based on the first solution.
region.Begin("Function Imports");
foreach (EdmFunction edmFunction in container.FunctionImports)
{
var parameters = FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
string paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
// Here is why a Function without return value is ignored:
if (edmFunction.ReturnParameter == null)
{
continue;
}
string returnTypeElement = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage));
...