Generic parsing of Expressiontree to SQL - c#

So, im having this very simple linq provider that does stuff with objects in a small constrained domain. No need for a full blown Linq2Sql implementation, but some of the stuff i need to do involves sql queries. My thougt was that, if you look away from table and column names, most expression tree => sql must be very (very) generic.
Expression<Func<DateTime, bool>> expr = d => d.DateTime.Month == 1
will always translate to
DATEPART({0}, m) = 1
or
Expression<Func<DateTime?, bool>> expr = d => d.HasValue || d == DateTime.Now
into
{0} IS NOT NULL OR {0} = NOW()
hope you get the point. Is there any generic sql generators like this out there?

hehe, dont we just looove hacking around? So, its not FULLY working yet, i need to figure out how to dynamically inject some mapping information, but so far it looks promising.
I've been able to produce this sql
SELECT t0.[Born], t0.[Yo]
FROM [Heies] AS t0
WHERE ((MONTH(t0.[Born]) = 1) OR (NOT (t0.[Yo] IS NULL OR t0.[Yo] = '') AND (t0.
[Born] = #p0)))
with this code
var provider = new DbEntityProvider(new SqlConnection(), new TSqlLanguage(), new ImplicitMapping(), new QueryPolicy());
var exp = provider.GetTable<Hey>().Where(d => d.Born.Month == 1 || (!String.IsNullOrEmpty(d.Yo) && d.Born == DateTime.Now)).Expression;
var sql = ((QueryProvider)provider).GetQueryText(exp);
using the IQToolkit, so thanks to Damian for reminding me about it again! If anyone has a similar way to produce sql via Linq2Sql i would love to hear from you so i could loose the dependency on IQToolkit.
Update
After a lot of fooling around i have hit the wall at extracting the values for each of the parameters. After a lot of source reading it seems like its hidden far away for the actual execution of the query. Well, guess what, i don't want the query to be executed :/
After looking into Linq2Sql i came up with this code that does roughly the same, so maybe i should just stick with that
var xml = Generate<NewsProperty>();
var mapping = XmlMappingSource.FromUrl(xml);
var ctx = new DataContext(new SqlConnection("..."), mapping);
var query = ctx.GetTable<NewsProperty>().Where(n => n.Date.Year == 2010).OrderBy(n => n.Date).Take(5);
var cmd = ctx.GetCommand(query);
string sql = cmd.CommandText;
foreach (DbParameter param in cmd.Parameters)
{
sql = sql.Replace(param.ParameterName, param.Value.ToString());
}

You should take a look at the IQueryable Toolkit and its related articles by Matt Warren http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx.

Related

How to compare date time in an anonymous entity type using LINQ. Entities does not recognize the method ToDateTime

I have an anonymous entity created like so :
var Results = (from c in db.SlotConfig
join dm in db.DailyMeters on
new { c.SlotID, c.SiteID } equals new { dm.SlotID, dm.SiteID }
select new {
Location = c.Location,
Manufacturer = c.ManufacturerName,
GameName = c.GameName,
GamingDay = dm.GamingDay,
...
});
where gaming day is a nullable DateTime(DateTime?).
Later on I have to get only the Results for a specific gaming day, thus using
var startResults = Results.Where(r => r.GamingDay.Value == Convert.ToDateTime("2018-06-11 00:00:00.000")); for example and it fires the exception.
When I do something like Results = Results.Where(m => m.SiteID == 1) it works perfectly fine. SiteID is nullable int, so I guess it works on the primitive types.
I checked for similar cases, but really nowhere is explained why it doesn't work.
Thanks in advance.
I guess the exception you're getting is something like "LINQ Entities doesn't know the Convert.ToDateTime()"? It's totaly normal, because Entity Framework is trying to translate your expression into a SQL Query, but Convert.ToDateTime doesn't exists in SQL (or not that way...). So you have to create your DateTime before passing it to your query.
Try something like that:
var myDate = Convert.ToDateTime("2018-06-11 00:00:00.000");
var startResults = Results.Where(r => r.GamingDay.Value == myDate);
And it should do the tricks.
A little explanation: with that way, you're passing to EF a value with a known type (here it's DateTime), not a function, so it's "easier" for EF to translate your expression into a SQL query.

PostgreSQL, Linq C# error 'could not determine data type of parameter $1'

I am getting this error "could not determine data type of parameter $1"
and my where clause where I am getting the error is like the following:
var result = from Table in model.Table
where (filter.XId.HasValue ? Table.XId == filter.XId: true)
select new TableEntity
{
ID = Table.XId
};
If my code was only like this 'Table.X == filter.X', it works ...
How can I fix this?
and I am getting this problem only with PostgreSQL database ....
First about what that error usually means. When making parametrized queries to PostgreSQL, all parameters should be referenced in query itself. When you add more parameters than used in query, usually the error above appears.
It seems that when whatever EF provider for PosgreSQL you use converted your statement to SQL, it created more parameters than needed.
In general, it might be hard for EF providers to analyze and correctly parse statements like you used, so good practice is to use statements that are "closer" to SQL in certain sense. In your case equivalent query which is "closer" to SQL would be:
where (filter.XId == null || Table.XId == filter.XId)
If you want to generate different queries based on the value of filter, you can do something like this:
var query = (IQueryable<Table>) model.Table;
if (filter.XId != null) {
query = query.Where(row => row.XId == filter.XId);
}
var result = query.Select(row => new TableEntity {
Id = row.XId
});
As it is the first SO answer in Google -
Marked answer didn't help me in similar case (EF6 + Postgre), so I had to use .AsEnumerable();
Yeah, it could be bad for perfomance but it fitted well in my case.
So this one would work:
var query = model.Table
.AsEnumerable()
.Where (p => filter.XId.HasValue ? p.XId == filter.XId: true);

C# get data from database

I need to enhance a function of C# code. I am new to C# but I have strong java background.
My job is to write a new query, like JDBC, to get data from database.
In the method below, I didn't see any SQL query.
what does this line mean ? is this similar to Hibernate hql ?
from p in Session.Query<MyObject>() select p
Thanks
code:
public IPagingList<MyObject> ReadMyObjectItems(int start, int limit, IList<Filter> filters)
{
var criteria = Session.CreateCriteria<MyObject>();
if (limit != -1)
{
criteria.SetMaxResults(limit);
criteria.SetFirstResult(start);
}
if (filters != null)
{
foreach (var filter in filters)
{
criteria.Add(Restrictions.InsensitiveLike(filter.Field, "%" + filter.Value + "%"));
}
}
IList<MyObject> report = criteria.List<MyObject>();
int total = (from p in Session.Query<MyObject>() select p).Count();
var pagedResults = new PagingList<MyObject> { List = report, Total = total };
return pagedResults;
}
(from p in Session.Query<MyObject>() select p).Count();
We are simply getting a count of all the objects.
Refactor this to
Session.Query<MyObject>().Count(). It's easier to read and in this case LINQ is unnecessary.
It's Linq syntax. A powerful form of querying incorporated into .Net and different .Net frameworks.
In your case it seems you are using NHibernate and that line is using NHibernate Linq. (But the surrounding code is using Criteria.)
For composing queries in NHibernate you have the options of using
Linq
HQL
Criteria
which all have different pros and cons.
Either will be translated into SQL by NHibernate. The actual result will be fetched when you try to access it such as for instance reading an item in the result or converting the result to a List etc.
It seems that project is using an ORM like NHibernate. You can get more details here : http://nhibernate.info

How Would I Write This In LINQ2SQL?

I am slowly porting over an app from MySQL to use Linq2Sql - but one query has stumped me a bit.
SELECT * FROM Pages WHERE DomainID = #reportid AND (PageContent REGEXP 'display:[ \t]*none') > 0 ORDER BY URL ASC
Any ideas on how I would write something like this with Linq2SQL? Its the REGEXP bit thats got me stumped?
There is no way built in to LINQ to SQL, but you have a couple of other choices. The first is to load your strings in as in-memory objects which you can apply Regex functions to. I'm not a big fan of this since it looks like you're potentially getting some very big strings to match against.
The second option is to leverage SQL CLR as described here. This effectively lets you create a stored procedure that gets linked to a CLR method that you create. Whenever you call the method in a LINQ to SQL context, it gets converted to a stored procedure call. Then you use a query like this:
var q = from p in context.Pages
where p.DomainId == reportId &&
RegExMatch(p.PageContent, "display\:[ \t]*none")
select p;
Why not use LINQ to return items that match on reportid and that contain 'display:', to minimise the amount of data being returned from the server, and then use regex on client side to filter that list down?
var query = Pages.Where( p => p.DomainId == 1 && p.PageContent.IndexOf("display:") > 0).OrderBy( o => o.URL );
var regex = new Regex(#"display\:[\t]*none");
foreach (var page in query)
{
if( regex.IsMatch(page.PageContent) )
{
// Do whatever...
}
}

Object mapping with LINQ and SubSonic

I'm building a small project with SubSonic 3.0.0.3 ActiveRecord and I'm running into an issue I can't seem to get past.
Here is the LINQ query:
var result = from r in Release.All()
let i = Install.All().Count(x => x.ReleaseId == r.Id)
where r.ProductId == productId
select new ReleaseInfo
{
NumberOfInstalls = i,
Release = new Release
{
Id = r.Id,
ProductId = r.ProductId,
ReleaseNumber = r.ReleaseNumber,
RevisionNumber = r.RevisionNumber,
ReleaseDate = r.ReleaseDate,
ReleasedBy = r.ReleasedBy
}
};
The ReleaseInfo object is a custom class and looks like this:
public class ReleaseInfo
{
public Release Release { get; set; }
public int NumberOfInstalls { get; set; }
}
Release and Install are classes generated by SubSonic.
When I do a watch on result, the Release property is null.
If I make this a simpler query and watch result, the value is not null.
var result = from r in Release.All()
let i = Install.All().Count(x => x.ReleaseId == r.Id)
where r.ProductId == productId
select new Release
{
Id = r.Id,
ProductId = r.ProductId,
ReleaseNumber = r.ReleaseNumber,
RevisionNumber = r.RevisionNumber,
ReleaseDate = r.ReleaseDate,
ReleasedBy = r.ReleasedBy
};
Is this an issue with my LINQ query or a limitation of SubSonic?
I think the issue might be that you're essentially duplicating the functionality of the ORM. The key thing to understand is this line:
from r in Release.All()
This line returns a list of fully-populated Release records for every item in your database. There should never be a need to new up a release anywhere else in your query - just return the ones that SubSonic has already populated for you!
Using this logic, you should be able to do the following:
var result = from r in Release.All()
select new ReleaseInfo {
Release = r,
NumberOfInstalls = Install.All().Count(x => x.ReleaseId == r.Id)
};
That being said, you should look at the Install.All() call, because that's likely to be tremendously inefficient. What that will do is pull every install from the database, hydrate those installs into objects, and then compare the id of every record in .NET to check if the record satisfies that condition. You can use the .Find method in SubSonic to only return certain records at the database tier, which should help performance significantly. Even still, inflating objects may still be expensive and you might want to consider a view or stored procedure here. But as a simple first step, the following should work:
var result = from r in Release.All()
select new ReleaseInfo {
Release = r,
NumberOfInstalls = Install.Find(x => x.ReleaseId == r.Id).Count()
};
I think I've found the actual answer to this problem. I've been rummaging around in the SubSonic source and found that there are two types of object projection that are used when mapping the datareader to objects: one for anonymous types and groupings and one for everything else:
Here is a snippet: Line 269 - 298 of SubSonic.Linq.Structure.DbQueryProvider
IEnumerable<T> result;
Type type = typeof (T);
//this is so hacky - the issue is that the Projector below uses Expression.Convert, which is a bottleneck
//it's about 10x slower than our ToEnumerable. Our ToEnumerable, however, stumbles on Anon types and groupings
//since it doesn't know how to instantiate them (I tried - not smart enough). So we do some trickery here.
if (type.Name.Contains("AnonymousType") || type.Name.StartsWith("Grouping`") || type.FullName.StartsWith("System.")) {
var reader = _provider.ExecuteReader(cmd);
result = Project(reader, query.Projector);
} else
{
using (var reader = _provider.ExecuteReader(cmd))
{
//use our reader stuff
//thanks to Pascal LaCroix for the help here...
var resultType = typeof (T);
if (resultType.IsValueType)
{
result = reader.ToEnumerableValueType<T>();
}
else
{
result = reader.ToEnumerable<T>();
}
}
}
return result;
Turns out that the SubSonic ToEnumerable tries to match the column names in the datareader to the properties in the object you're trying to project to. The SQL Query from my Linq looks like this:
SELECT [t0].[Id], [t0].[ProductId], [t0].[ReleaseDate], [t0].[ReleasedBy], [t0].[ReleaseNumber], [t0].[RevisionNumber], [t0].[c0]
FROM (
SELECT [t1].[Id], [t1].[ProductId], [t1].[ReleaseDate], [t1].[ReleasedBy], [t1].[ReleaseNumber], [t1].[RevisionNumber], (
SELECT COUNT(*)
FROM [dbo].[Install] AS t2
WHERE ([t2].[ReleaseId] = [t1].[Id])
) AS c0
FROM [dbo].[Release] AS t1
) AS t0
WHERE ([t0].[ProductId] = 2)
Notice the [t0].[c0] is not the same as my property name NumberOfInstalls. So the value of c0 never gets projected into my object.
THE FIX:
You can simply take out the if statement and use the 10x slower projection and everything will work.
We have a bug with projections that trips on certain occassions - I think it's been patched but I need to test it more. I invite you to try the latest bits - I think we've fixed it... sorry to be so vague but a bug worked it's way in between 3.0.0.1 and 3.0.0.3 and I haven't been able to find it.
Has this been fixed in 3.0.0.4? I was so peeved to find this post. After 2 days of trying to figure out why my projections were not working - except when the property names matched the query exactly - I ended up here.
I am so dependant on SS SimpleRepository that it is too late to turn back now. A bug like this is crippling. Any chance it is sorted out?
I went the 10x slower route for now so I can at least release to my client. Would much prefer the faster method to work correctly :)

Categories

Resources