"No supported translation in SQL" Linq workaround? - linq

Currently im trying to make my query short with reusable peice of code like this to check for post if it's eligible to display.
// Logic to check if post is eligible for display
public bool isEligibleForDisplay(Post n)
{
var pubDate = n.PUBLISH_DATE ?? DateTime.MinValue;
var endDate = n.END_DATE ?? DateTime.MaxValue;
var correctState = (n.STATE == PostState.Publish || n.STATE == PostState.Furture);
var dateInRange = (DateTime.Now >= pubDate && DateTime.Now <= endDate);
return correctState && dateInRange;
}
my linq look like this:
var q = from n in _db.Posts
where isEligibleForDisplay(n)
group n by n.POST_ID into g
select g.OrderByDescending(t => t.CREATE_DATE).First();
return q.Take(quantity);
I ran into "No supported translation in SQL" problem for the first time of using linq to sql, I am just wondering if there are anyway that can use as a work around for this case, which could be troublesome if I include whole lot of those checking logic into my query everytime.
I'm so looking forward for a reply. Thanks!

You can create a function on your SQL server called isEligibleForDisplay that does the SQL equivalence of these checkes and add that to your dbml file.
I haven't tested this, but I'm thinking the easiest would be if you create a function where you pass the values you want, rather than the whole record, and I think something like this might work:
CREATE FUNCTION isEligibleForDisplay(
#publishDate DATETIME,
#endDate DATETIME,
#state TINYINT -- correct me if i'm wrong...
) RETURNS bit
AS
BEGIN
DECLARE #return bit
DECLARE #dateStart DATETIME, #dateEnd DATETIME
SET #return = 0
SET #dateStar t= COALESCE(#publishDate, CONVERT(DATETIME, '1900-01-01'))
SET #dateEnd = COALESCE(#endDate, CONVERT(DATETIME, '9999-12-31'))
IF getdate() BETWEEN #dateStart AND #dateEnd
BEGIN
IF #state IN(1,3) -- or whatever the int representations of your enum are
SET #return = 1
END
RETURN #return
END

I used the extension of linq to include a method, which can actually work very fine using IQueryable.
public static IQueryable<T> getPostActive<T>(this IQueryable<T> items) where T : P015.Models.SQLModel.Post
{
// Logic to check if post is eligible for display
var now = DateTime.Now;
return items.Where(n =>
(n.STATE.Trim() == PostState.Publish || n.STATE.Trim() == PostState.Furture || n.STATE.Trim() == PostState.Draft) &&
(
((n.END_DATE ?? SqlDateTime.MaxValue.Value) >= now) &&
((n.PUBLISH_DATE ?? SqlDateTime.MinValue.Value) <= now)
)
);
}

How maqny records are in _db.Posts? If not much, you can do .ToList() first, and than linq will be able to use isEligibleForDisplay function.

Related

How to declare a System.Linq.IQueryable variable with a complex query

I have a working query as below:
var result = from sch in schemeDashboard
join exp in Expenditure on sch.schemeId equals exp.SchemeCode
into SchExpGroup
where sch.SectorDepartmentId == selectedDepartmentId &&
sch.YearCode == StateManager.CurrentYear
orderby sch.ADPId
select new
{
ProjectName = sch.schemeName,
ADPNo = sch.ADPId,
Allocation = sch.CurrentAllocation,
Expenditures = from expend in SchExpGroup
where expend.YearCode == StateManager.CurrentYear &&
expend.DepartmentId == selectedDepartmentId &&
InvStatus.Contains(expend.Status)
orderby expend.ADPId
group expend by expend.InvoiceId
};
Now I need to declare "result" as: System.Linq.IQueryable<...> result = null; so that I can write the query in various If-else blocks with minor modification. (projectName is string, ADPNo is int, Allocation is decimal).
I have tried as follow:
System.Linq.IQueryable<string, int, decimal,
System.Collections.Generic.IEnumerable<System.Linq.IGrouping<string, Expenditure>>> result = null;
But this is giving error as: The non-generic type 'IQueryable' cannot be used with typed arguments.
Can anyone point me in right direction? thanx.
The most generical class of C# is the class object. So you can declare your list with the Object type.
IQueryable <object> result = null;
Well I hope it works for you as it worked for me.

Linq DateTime comparison not working

I have the following code:
DateTime timeStamp = Convert.ToDateTime(Request.QueryString["TimeStamp"]);
var result = (from rs in db.VRec
where
rs.TimeStamp == timeStamp &&
rs.Fixure == wFixture
select rs).ToList();
The result shows 0 even though the correct timeStamp is passed.
If I remove the part where I do the TimeStamp comparison:
rs.TimeStamp == timeStamp
The code works fine.
Any idea on why the datetime comparison may not be working?
DateTime has a pretty fine resolution - likely you are comparing timestamps that only differ in milliseconds, which will fail. You probably want something like:
DateTime now = DateTime.Now;
DateTime then = now.Add(TimeSpan.FromMilliseconds(1));
const int EPSILON_MS = 10;
if(now.Subtract(then).TotalMilliseconds < EPSILON_MS)
{
Console.WriteLine("More or less equal!");
}
Linq converts DateTime arguments to DateTime2 in the sql query executed.
That is, when you do the comparison the actual sql executed will compare a DateTime to a DateTime2. This comparison will "cast" the DateTime to a DateTime2 and the millisecond part will be expanded to a greater resolution (in an odd way in my opinion, please enlighten me).
Try to execute the following sql:
declare #d1 datetime = '2016-08-24 06:53:01.383'
declare #d2 datetime2 = '2016-08-24 06:53:01.383'
declare #d3 datetime2 = #d1
select #d1 as 'd1', #d2 'd2', #d3 'converted'
select (case when (#d1 = #d2) then 'True' else 'False' end) as 'Equal',
(case when (#d1 > #d2) then 'True' else 'False' end) as 'd1 greatest'
From the question, I do not know if you want to compare the date with time or only the date part. If you only want to compare date then following would work
var result = (from rs in db.VRec
where
rs.TimeStamp.Date == timeStamp.Date &&
rs.Fixure == wFixture
select rs).ToList();
Since you are using some reference to db, it gives me a feeling that you are fetching your records from database (which ORM you are using is not obvious from the question or tags). Assuming that you are using Entity framework the above query will fail with exception that .Date has no direct translation to sql. If so you can rewrite the query as following to make it work.
var result = (from rs in db.VRec
where
rs.TimeStamp.Day == timeStamp.Day &&
rs.TimeStamp.Month == timeStamp.Month &&
rs.TimeStamp.Year == timeStamp.Year &&
rs.Fixure == wFixture
select rs).ToList();
The benefit of this approach is that you can compare properties to arbitrary deep level i.e you can compare Hours, Minutes,Seconds etc. in your query. The second query is tested in Entity framework 5.

How can I make my Dynamic Linq query more efficient

I've written an mvc controller function to handle dynamic queries, it's working great but with some more complicated queries it's really bogging down as much as 15 second response time. I used ObjectQuery.ToTraceString to get the sql and execute it against my database and I'm getting 1-2 second response times, not great but hugely disparate from the time it takes for my server to respond to the same query.
Here is my code:
public class QueryController : Controller
{
//
// GET: /Query/
Entities1 Context = new Entities1();
public JsonResult Query(
string select,
string table = "Product",
string where = "",
string groupBy = ""
)
IQueryable dataTable;
if (table == "Customer") dataTable = Context.Customers;
else if (table == "Product") dataTable = Context.Products;
else if (table == "Purchase") dataTable = Context.Purchase;
else dataTable = Context.Farms;
if (select == null) return null;
string whereClause = where;
string selectClaus = select;
string groupByClaus = groupBy;
IQueryable queryResults = dataTable;
if (where != "") queryResults = queryResults.Where(whereClause);
if (groupByClaus != "") queryResults = queryResults.GroupBy(groupByClaus, "it");
queryResults = queryResults.Select(selectClaus);
JsonResult result = new JsonResult();
result.Data = queryResults;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
}
This code is supposed to handle requests like this:
?table=Purchase
&select=new (Customer.First as Name, Product.Id as ProdId)
&where=Category == 5
A query like the above takes about 700 ms, but if I try something more complicated it slows to a crawl (15 seconds):
?table=Purchase
&select=new (
count(true) as Total,
Key.Product as Product,
Key.CustomerRef as CustomerRef,
count(Price > 475) as BigPurchases,
count(PaidFor == false) as UnPaid,
count((Category != null) and (Comments == null) and Returns == null) as NoFeedback)
&groupBy=new (
Product.ProductName as Product,
CustomerRef as CustomerRef
)
In particular the navigation Property seems to be a problem, removing it speeds up the query considerably (3 sec):
?table=Purchase
&select=new (
count(true) as Total,
Key.Product as Product,
Key.CustomerRef as CustomerRef,
count(Price > 475) as BigPurchases,
count(PaidFor == false) as UnPaid,
count((Category != null) and (Comments == null) and Returns == null) as NoFeedback)
&groupBy=new (
ProductRef as Product,
CustomerRef as CustomerRef
)
The time all seems to get used iterating over the IEnumerable, in the code I provide I pass the data out and let the whatever underlying MVC code do whatever converting it wants, which takes approximately the time mentioned. However if I iterate over it myself, or use the ToList function I get similarly slow response times.
Does anyone have any ideas as to what is causing the long pause getting these entities?
Update
I added indexing to my database which sped things up, but it is still taking 20-30 times as long to execute the query in linq than it does in sql.

Simplifying LINQ query?

I use this LINQ query in an app I am writing:
internal int GetNoteCount(DateTime startDate, DateTime endDate)
{
var a = DataStore.ObjectContext.Notes.Where(n => n.LastRevised >= startDate);
var b = a.Where(n => n.LastRevised <= endDate);
return b.Count();
}
Obviously, the query gets Notes that fall between two dates. I'd like to simplify the query by consolidating the first two lines into one. I know I can use fluent syntax to add the Count() method call to the end of my query.
Here's my question: How can I consolidate the two queries? Apparently, the && operator doesn't work with two lambda expressions. Thanks for your help.
You can do this:
internal int GetNoteCount(DateTime startDate, DateTime endDate)
{
return DataStore.ObjectContext.Notes.Where(n => n.LastRevised >= startDate && n.LastRevised <= endDate).Count();
}
Just use the && on your conditions, not the entire lambda :)
First, there's nothing wrong with the && operator and it should work just fine.
var a = DataStore.ObjectContext.Notes.Where(n => n.LastRevised >= startDate && n.LastRevised <= endDate);
return a.Count();
Second, with LINQ, it delays performing the query until the .Count() so there is no functional difference between your example and this one.

date difference with linq

With this code:
i.SpesaAlloggio = db.TDP_NotaSpeseSezB.Sum(p => p.Costo / (((DateTime)p.DayEnd)
.Subtract((DateTime)p.DayStart).Days + 1));
I receive this error:
LINQ to Entities does not recognize the method
'System.TimeSpan Subtract(System.DateTime)' method, and this method cannot be
translated into a store expression.
How can I do this?
Use a calculated DB field and map that. Or use SqlFunctions with EF 4 as LukLed suggested (+1).
I wrote a function for removing time:
public static DateTime RemoveHours(DateTime date)
{
int year = date.Year;
int month = date.Month;
int day = date.Day;
return new DateTime(year, month, day);
}
and changed filtering condition:
var query =
from trn in context.IdentityTransactions
where trn.ClientUserId == userId && trn.DateDeleted == null
orderby trn.DateTimeCreated
select new
{
ClientServerTransactionID = trn.ClientServerTransactionID,
DateTimeCreated = trn.DateTimeCreated,
ServerTransDateTime = trn.ServerTransDateTime,
Timestamp = trn.Timestamp,
Remarc = trn.Remarc,
ReservedSum = trn.ReservedSum,
};
if (dateMin.HasValue && dateMin.Value > DateTime.MinValue)
{
DateTime startDate = Converters.RemoveHours(dateMin.Value);
query = from trn in query
where trn.DateTimeCreated >= startDate
select trn;
}
if (dateMax.HasValue && dateMax.Value > DateTime.MinValue)
{
var endDate = Converters.RemoveHours(dateMax.Value.AddDays(1.0));
query = from trn in query
where trn.DateTimeCreated < endDate
select trn;
}
dateMin and dateMax are nullable types and may be not set in my case.
Try (it is not very efficient, but it will work):
i.SpesaAlloggio = db.TDP_NotaSpeseSezB.ToList()
.Sum(p => p.Costo / (((DateTime)p.DayEnd)
.Subtract((DateTime)p.DayStart).Days + 1));
EDIT : This will be extremely slow for large tables, because it transfers whole table content form server
Entity Framework tries to translate your expression to SQL, but it can't handle ((DateTime)p.DayEnd).Subtract((DateTime)p.DayStart). You have to make it simpler. ToList() gets all rows and then makes the calculation on application side, not in database.
With EF4, you could use SqlFunctions DateDiff
With EF1, you could create calculated field or view with this field and make calculation based on this field.

Resources