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?
Related
In a C# project, I want to be able to create a function that accepts an IQuereable<User> object along with multiple lambda's expressions then converts it into a different object. However, instead of pulling all the available properties from the database, I want to pull only the properties that are provided to the function. Here is the logic that I think I need to follow
Figure out what properties that I need to select
Construct an IEnumerable from the IQueryable by specifying which properties that are needed.
Iterate over every property that was selected and create the Student object.
In other words, if I call .ToList() on the IQuereable<User> users object, the query will select * from the databse table which pull all available columns. Instead I want to select only the properties that are passed as labda expressions.
Here is my code
public IEnumerable<Student> Make(IQuereable<User> users, Expression<Func<User, dynamic>> keyProperty, params Expression<Func<User, dynamic>>[] propertiesToSelect)
{
var students = new List<Student>();
// Make a distinct list of lambda's expressions that I need to select
var props = propertiesToSelect.ToList();
props.Add(keyProperty);
props = props.Distinct().ToList();
// TO DO!!! Some how, I need to only select the properties that are in **props** array insted of pulling all available properties
var selectableUsers = users.Select(/**/).ToList();
foreach(var user in selectableUsers)
{
var student = new Student();
foreach(Expression<Func<User, object> exp in props)
{
var prop = GetPropertyInfo(user, exp)
object value = prop.GetValue(user, null);
// Set the property student property
// Do somthing with prop.Name...
// Do something with value...
}
students.Add(student);
}
return strudents;
}
Question How can I use LINQ to select only the list of Expressions
This may be a case for using a stored procedure and dynamic SQL to build your initial query and control the contents of the SELECT statement.
The other option I see is to define a DTO object and leverage the AutoMapper Queryable Extensions and use the Explicit Expansion functionality to control the data to return.
I have a problem similar to this:
How to retrieve multiple columns from non-entity type sql query?
I need to implement the method string[,] DirectQuery(string sqlText, string[] param) which is basically a C# equivalent of SQL Server Management Studio.
The user is supposed to enter a SQL query as string text (+ string parameters to avoid SQL injection) and receive back a string matrix containing the outcome of the query.
Internally, I'm using Entity Framework.
Here's my implementation:
public string[,] DirectQuery(string sqlQuery, string[] param)
{
//discover how many fields are specified in the select clause
string ip = sqlQuery.ToLower().Split(new string[] { "from" }, StringSplitOptions.None)[0];
int cols = ip.Count(y => y == ',') + 1;
//execute the query
DbRawSqlQuery<string> res = param != null ? _context.Database.SqlQuery<string>(sqlQuery, param) : _context.Database.SqlQuery<string>(sqlQuery);
//wrap everything in a matrix to return
return res.ToArray().Array2Matrix(res.ToArray().Length /cols, cols);
}
where
public static T[,] Array2Matrix<T>(this T[] flat, int rows, int cols) where T : class
is my custom method that turns flat arrays into rows x cols matrices.
If in the select clause users specify a single attribute, that works fine, but in case of 2+ fields needed the execution of DirectQuery fires the runtime exception dbrawsqlquery he data reader has more than one field. Multiple fields are not valid for EDM primitive or enumeration types. That's completely reasonable, but since the query can be whatever I can't create a custom class to wrap every possible outcome.
What do you suggest?
The problem is that you're using a method - DbRawSqlQuery which must be told what type to expect, and you're telling it to expect just a string, so it has no idea what to do with more than one returned column.
Maybe it would work if you specified string[] or IEnumerable<string> or something? Alternatively you could define a series of objects with 1, 2, 3, 4 etc values and detect the number of items at runtime and use the correct class... but that seems absurd.
Really though, I'd suggest NOT using EF as someone suggested above. Find something which can return dynmamic objects, OR just use ADO.Net directly.
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.
I'm busy with a LINQ to SQL project that basically creates multiple threads for each entity type in my database, which constantly queries information from the DB in a thread.
Here's a pseudo example:
streamer.DefineDataExpression<Contacts>(x => x.FirstName == "Bob");
while(true)
{
List<Contacts> MyContactsResult = streamer.ResultList;
// do whatever with MyContactsResult
}
The above code doesn't exist, but this is what I have so far for the 'streamer' class (it obviously doesn't work, but you can see what I'm trying to achieve above):
public void DefineExpression(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
using (var db = new LINQDataContext())
{
ResultList = db.GetTable<T>().Where(expression);
}
}
How do I go about creating a method like 'DefineExpression' that will allow me to query a LINQ type dynamically?
Why not use the Dynamic LINQ provider, as mentioned by Scott Guthrie. I think that would give you everything you are looking for, because you can define the query as a string. Therefore, you can more easily build a string representation of your query, and execute on the fly.
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)
...