Parsing Expression Tree To Sqlstring - Not Reinventing the wheel - sql

I need to parse an expressiontree to get a sql where clause.
Aren't there any classes in the .NET FX or any third party library which already have this abilities ?
I mean Linq2SQL, EntityFramework , all of them have to do this, so does anyone know, if there is something that can be reused instead of reinventing the wheel?
MyType.Where(Func<TEntity,bool>((entity)=>entity.Id == 5)))
now i need to get the corresponding string representing a where clause:
where abc.Id = "5"
this is just an simple example. it should also work with logical conjunctions.
I know I can create the expressiontree and parse it on my own, but i think there could be something already existing, which I'm missing

You could create an ExpressionVisitor with the sole purpose of finding and processing the where calls. Making this task very easy to handle.
e.g.,
public class WhereCallVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var method = node.Method;
if (method.Name == "Where" && method.DeclaringType == typeof(Queryable))
{ // we are in a Queryable.Where() call
ProcessWhereCall(node);
}
return base.VisitMethodCall(node);
}
private void ProcessWhereCall(MethodCallExpression whereCall)
{
// extract the predicate expression
var predicateExpr = ((dynamic)whereCall.Arguments[1]).Operand as LambdaExpression;
// do stuff with predicateExpr
}
// p.s., dynamic used here to simplify the extraction
}
Then to use it:
var query = from row in table
where row.Foo == "Bar"
select row.Baz;
var visitor = new WhereCallVisitor();
visitor.Visit(query.Expression);

Related

How to add a semi colon ; automatically to each generated sql statement using jOOQ

I'm trying to add a semi colon ; to every jOOQ generated sql statement as I'm writing multiple DDL and insert statement to an output file.
I found a similar question here suggesting using an ExecuteListener here https://jooq-user.narkive.com/6adKecpt/adding-semicolon-at-the-end-of-sql-statement.
My setup is now as follows (using Groovy):
private DSLContext createDSLContext() {
def configuration = new DefaultConfiguration()
configuration.settings = new Settings()
.withRenderFormatted(true)
.withRenderKeywordCase(RenderKeywordCase.LOWER)
.withRenderQuotedNames(RenderQuotedNames.ALWAYS)
.withStatementType(StatementType.STATIC_STATEMENT)
configuration.set(
new DefaultExecuteListenerProvider(new DefaultExecuteListener() {
#Override
void renderEnd(ExecuteContext ctx) {
ctx.sql(ctx.sql() + ";")
}
}),
new DefaultExecuteListenerProvider(new DefaultExecuteListener() {
#Override
void start(ExecuteContext ctx) {
println "YEAH!!!"
}
}))
// return configuration.dsl();
return DSL.using(configuration)
}
but is not adding the semi colon, nor is it getting in the renderEnd method at all.
I added another execute listener to print something at the start (as I have seen in other examples) but it is also never called..
My code looks like:
file.withWriter { writer ->
// Drop schema objects.
DEFAULT_SCHEMA.tables.each {
switch (it.type) {
case TABLE:
writer.writeLine(dsl.dropTableIfExists(it).SQL)
break
case VIEW:
writer.writeLine(dsl.dropViewIfExists(it).SQL)
break
}
}
writer.writeLine("")
// Create schema objects.
def ddlStatements = dsl.ddl(DEFAULT_SCHEMA)
ddlStatements.each {
writer.writeLine(it.SQL)
writer.writeLine("")
}
// Insert data.
def insert = dsl.insertInto(Tales.CUSTOMER).columns(Tales.CUSTOMER.fields())
customers.each {insert.values(it) }
writer.writeLine(insert.SQL)
}
The ExecuteListener lifecycle is only triggered when you execute your queries with jOOQ. You're not doing that, you're just calling Query.getSQL()
You could wrap your queries into DSLContext.queries(Query...), and jOOQ will separate the statements using ; when you call Queries.getSQL() when you call Queries.toString(). Of course, that's not reliable, the behaviour of toString() might change in the future, which is why it would make sense to offer methods like Queries.getSQL() and the likes: https://github.com/jOOQ/jOOQ/issues/11755
For the time being, why not just add the semi colon manually to the writer in your code?

Executing a Remote.Linq expression against data

I'm using Remote.Linq to serialise / deserialise my Expressions as I want to create the ability to send dynamic expressions from a client application to our web services. Standard .NET expressions cannot be serialised so I'm using Remote.Linq instead.
However, I cannot see how to execute the Expression. Normally I would invoke the Compile() and Invoke() methods to execute the Expression against the data. But Remote.Linq expressions don't support such methods.
The following unit test may explain more clearly what I'm trying to achieve.
[TestMethod]
public void SerializeLinqExpressionsTests()
{
var testdata = GetTestdata();
Expression<Func<ModuleEntityAdmins, ModuleEntityAdmin>> expr1 = m => m.Modules.Find(q => q.Id == 1);
var remoteExpression1 = expr1.ToRemoteLinqExpression();
string strexpr1 = SerialiseExpression(remoteExpression1);
try
{
var deserexpr1 = DeserialiseExpression<Remote.Linq.Expressions.LambdaExpression>(strexpr1.NormalizeJsonString());
//what is the equivalent of doing this with a Remote.Linq Expression?
var compiled1 = expr1.Compile();
var result = compiled1.Invoke(testdata);
Assert.IsNotNull(result);
Assert.IsTrue(result.Id == 1);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Assert.Fail("Error deserialising LINQ expression tree");
}
}
How do you invoke a Remote.Linq expression?
A remote linq expression may be converted back into a system linq expression and be compiled and executed as such.
However, this is not what you actually want when sending expressions to a server to query data. On server side you want to use the Execute extension methods to execute your expression against a data source. Make sure to add a using for namespace Remote.Linq.Expressions.
Here's the sample code from Remote.Linq source repo:
using Remote.Linq.Expressions;
public interface IQueryService
{
IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression);
}
public class QueryService : IQueryService, IDisposable
{
// any linq provider e.g. entity framework, nhibernate, ...
private IDataProvider _datastore = new ObjectRelationalMapper();
// you need to be able to retrieve an IQueryable by type
private Func<Type, IQueryable> _queryableProvider = type => _datastore.GetQueryableByType(type);
public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
{
// `Execute` is an extension method provided by Remote.Linq
// it applies an expression to a data source and returns the result
return queryExpression.Execute(queryableProvider: _queryableProvider);
}
public void Dispose() => _datastore.Dispose();
}
Also, there are additional nuget packages for expression execution with EF and EF Core, so you can simply provide a DbContext to the Execute method.
In addition you may want to check out the demos/samples found in the project's github repo.

Returning distinct data for a dropdownlist box with selectlistItem

I have a field in my database with duplicates. I want to use it in a dropdown list, which has to return distinct data.
Here is the method that I created to do this:
public IEnumerable<SelectListItem> GetBranches(string username)
{
using (var objData = new BranchEntities())
{
IEnumerable<SelectListItem> objdataresult = objData.ABC_USER.Select(c => new SelectListItem
{
Value = c.BRANCH_CODE.ToString(),
Text = c.BRANCH_CODE
}).Distinct(new Reuseablecomp.SelectListItemComparer());
return objdataresult;
}
}
Here is the class I am using:
public static class Reuseablecomp
{
public class SelectListItemComparer : IEqualityComparer<SelectListItem>
{
public bool Equals(SelectListItem x, SelectListItem y)
{
return x.Text == y.Text && x.Value == y.Value;
}
public int GetHashCode(SelectListItem item)
{
int hashText = item.Text == null ? 0 : item.Text.GetHashCode();
int hashValue = item.Value == null ? 0 : item.Value.GetHashCode();
return hashText ^ hashValue;
}
}
}
Nothing is returned and I get the error below. When I try a basic query without Distinct, everything works fine.
{"The operation cannot be completed because the DbContext has been disposed."}
System.Exception {System.InvalidOperationException}
Inner exception = null
How can I return distinct data for my dropdown?
Technically, your problem can be solved simply by appending .ToList() after your Distinct(...) call. The problem is that queries are evaluated JIT (just in time). In other words, until the actual data the query represents is needed, the query is not actually sent to the database. Calling ToList is one such thing that requires the actual data, and therefore will cause the query to be evaluated immediately.
However, the root cause of your problem is that you are doing this within a using statement. When the method exits, the query has not yet been evaluated, but you have now disposed of your context. Therefore, when it comes time to actually evaluate that query, there's no context to do it with and you get that exception. You should really never use a database context in conjuction with using. It's just a recipe for disaster. Your context should ideally be request-scoped and you should use dependency injection to feed it to whatever objects or methods need it.
Also, for what it's worth, you can simply move your Distinct call to before your Select and you won't need a custom IEqualityComparer any more. For example:
var objdataresult = objData.ABC_USER.Distinct().Select(c => new SelectListItem
{
Value = c.BRANCH_CODE.ToString(),
Text = c.BRANCH_CODE
});
Order of ops does matter here. Calling Distinct first includes it as part of the query to the database, but calling it after, as you're doing, runs it on the in-memory collection, once evaluated. The latter requires, then, custom logic to determine what constitutes distinct items in an IEnumerable<SelectListItem>, which is obviously not necessary for the database query version.

How to pass a parameter in my method to return using a stateless session? minimize code duplication

I want to pass a parameter in my method call, if set (its a boolean), then return a Stateless session.
I don't want to duplicate the QueryOver code, is there a way to have it like:
public virtual IList<User> GetAllUsers(bool isStateless)
{
var query = QueryOver<User>().Where(x => x.UserType == 1).ToList();
if(isStateless)
return NHibernateHelper.Session(query);
else
return NHibernateHelper.StatelessSession(query);
}
I know the above won't work, but I hope it is clear what I am after.
The only way I know is to basically duplicate the entire queryover code, and the only different between the code blocks will be that one will use .Session and the other will use .StatelessSession.
Hoping there is a cleaner way.
var query = QueryOver.Of<User>().Where(x => x.UserType == 1);
IQueryOver<User, User> executableQuery;
if(isStateless)
executableQuery = query.GetExecutableQueryOver(NHibernateHelper.Session);
else
executableQuery = query.GetExecutableQueryOver(NHibernateHelper.StatelessSession);
return executableQuery.ToList();

How to intercept and modify SQL query in Linq to SQL

I was wondering if there is any way to intercept and modify the sql generated from linq to Sql before the query is sent off?
Basically, we have a record security layer, that given a query like 'select * from records' it will modify the query to be something like 'select * from records WHERE [somesecurityfilter]'
I am trying to find the best way to intercept and modify the sql before its executed by the linq to sql provider.
Ok, first to directly answer your question (but read on for words of caution ;)), there is a way, albeit a finicky one, to do what you want.
// IQueryable<Customer> L2S query definition, db is DataContext (AdventureWorks)
var cs = from c in db.Customers
select c;
// extract command and append your stuff
DbCommand dbc = db.GetCommand(cs);
dbc.CommandText += " WHERE MiddleName = 'M.'";
// modify command and execute letting data context map it to IEnumerable<T>
var result = db.ExecuteQuery<Customer>(dbc.CommandText, new object[] { });
Now, the caveats.
You have to know which query is generated so you would know how to modify it, this prolongs development.
It falls out of L2S framework and thus creates a possible gaping hole for sustainable development, if anyone modifies a Linq it will hurt.
If your Linq causes parameters (has a where or other extension causing a WHERE section to appear with constants) it complicates things, you'll have to extract and pass those parameters to ExecuteQuery
All in all, possible but very troublesome. That being said you should consider using .Where() extension as Yaakov suggested. If you want to centrally controll security on object level using this approach you can create an extension to handle it for you
static class MySecurityExtensions
{
public static IQueryable<Customer> ApplySecurity(this IQueryable<Customer> source)
{
return source.Where(x => x.MiddleName == "M.");
}
}
//...
// now apply it to any Customer query
var cs = (from c in db.Customers select c).ApplySecurity();
so if you modify ApplySecurity it will automatically be applied to all linq queries on Customer object.
If you want to intercept the SQL generated by L2S and fiddle with that, your best option is to create a wrapper classes for SqlConnection, SqlCommand, DbProviderFactory etc. Give a wrapped instance of SqlConnection to the L2S datacontext constructor overload that takes a db connection. In the wrapped connection you can replace the DbProviderFactory with your own custom DbProviderFactory-derived class that returns wrapped versions of SqlCommand etc.
E.g.:
//sample wrapped SqlConnection:
public class MySqlConnectionWrapper : SqlConnection
{
private SqlConnecction _sqlConn = null;
public MySqlConnectionWrapper(string connectString)
{
_sqlConn = new SqlConnection(connectString);
}
public override void Open()
{
_sqlConn.Open();
}
//TODO: override everything else and pass on to _sqlConn...
protected override DbProviderFactory DbProviderFactory
{
//todo: return wrapped provider factory...
}
}
When using:
using (SomeDataContext dc = new SomeDataContext(new MySqlConnectionWrapper("connect strng"))
{
var q = from x in dc.SomeTable select x;
//...etc...
}
That said, do you really want to go down that road? You'll need to be able to parse the SQL statements and queries generated by L2S in order to modify them properly. If you can instead modify the linq queries to append whatever you want to add to them, that is probably a better alternative.
Remember that Linq queries are composable, so you can add 'extras' in a separate method if you have something that you want to add to many queries.
first thing come to my mind is to modify the query and return the result in Non-LINQ format
//Get linq-query as datatable-schema
public DataTable ToDataTable(System.Data.Linq.DataContext ctx, object query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
IDbCommand cmd = ctx.GetCommand((IQueryable)query);
System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
adapter.SelectCommand = (System.Data.SqlClient.SqlCommand)cmd;
DataTable dt = new DataTable("sd");
try
{
cmd.Connection.Open();
adapter.FillSchema(dt, SchemaType.Source);
adapter.Fill(dt);
}
finally
{
cmd.Connection.Close();
}
return dt;
}
try to add your condition to the selectCommand and see if it helps.
Try setting up a view in the DB that applies the security filter to the records as needed, and then when retrieving records through L2S. This will ensure that the records that you need will not be returned.
Alternatively, add a .Where() to the query before it is submitted that will apply the security filter. This will allow you to apply the filter programmatically (in case it needs to change based on the scenario).