Linq Intersect(...).Any() inside of a Where clause throws NotSupportedException - c#

Whenever the tags argument is not empty I get a NotSupportedException:
Local sequence cannot be used in LINQ to SQL implementation of query operators except the
Contains() operator.
[WebMethod]
public static object GetAnswersForSurvey(string surveyName, int? surveyYear, IEnumerable<string> tags, IEnumerable<string> benchmarks)
{
IQueryable<DAL.Answer> results = new DataClassesDataContext().Answers
.OrderBy(a => a.Question.Variable);
if (!String.IsNullOrEmpty(surveyName)) results = results.Where(a => a.Survey.Name == surveyName);
if (surveyYear.HasValue) results = results.Where(a => a.Survey.Year == surveyYear.Value);
if (tags.Any()) results = results.Where(answer => answer.Question.Tags.Select(t => t.Label).Intersect(tags).Any());
if (benchmarks.Any()) results = results.Where(answer => benchmarks.Contains(answer.Question.BenchmarkCode));
return results.Select(a => new {
a.Question.Wording,
a.Demographic,
Benchmark = a.Question.BenchmarkCode,
a.Question.Scale,
a.Mean,
a.MEPMean,
a.NSSEMean
});
}
I understand it may not be possible to do it the way I'm trying. If it is impossible, can anyone offer any alternatives?

A number of the general purpose Linq to Object methods are not support within Linq to SQL.
http://msdn.microsoft.com/en-us/library/bb399342.aspx
An alternative might be to complete several sub-queries against sql and then perform your intersection operations as Linq to Objects

I think the reason is that the implementation of System.Data.Linq.Table.Intersect returns an IEnumerable, which in turn does not implement a parameterless version of Any().

Related

Use Linq Any query or Expressions to build query for performance

we are Entity Framework in our project. Need to know the performance impact between .ANY() and Expressions to form Where clause.
In the below function i used two approach to get result:
APPROACH 1 - Form Lambda expression query using ANY()
From my observation using .Any() is not adding where clause when query is executed(checked in sql profiler) what EF does is gets all matched inner joined records store in-memory and then apply condition specified in .ANY()
APPROACH 2 - Form Expression Query Starts
With Expressions i'm explicitly forming where clause and executing.checked the same in SQL query Profiler i'm able to see where clause.
Note: To form expression where clause i'm doing additional loops and "CombinePredicates".
Now, my doubts are:
which approach will improve performance. Do i need to go with Lambda
with .ANY() or Expressions?
what is the right way to from where clause to improve performance?
If not the two approach suggest me the right way to do it
private bool GetClientNotifications(int clientId, IList<ClientNotification> clientNotifications)
{
IList<string> clientNotificationList = null;
var clientNotificationsExists = clientNotifications?.Select(x => new { x.Name, x.notificationId
}).ToList();
if (clientNotificationsExists?.Count > 0)
{
//**Approach 1 => Form Lamada Query starts**
notificationList = this._clientNotificationRepository?.FindBy(x => clientNotificationsExists.Any(x1 => x.notificationId == x1.notificationId && x.clientId ==
clientId)).Select(x => x.Name).ToList();
**//Form Lamada Query Ends**
//**Approach 2 =>Form Expression Query Starts**
var filterExpressions = new List<Expression<Func<DbModel.ClientNotification, bool>>>();
Expression<Func<DbModel.ClientNotification, bool>> predicate = null;
foreach (var clientNotification in clientNotificationsExists)
{
predicate = a => a.clientId == clientId && a.notificationId == clientNotification .notificationId;
filterExpressions.Add(predicate);
}
predicate = filterExpressions.CombinePredicates<DbModel.ClientNotification>(Expression.OrElse);
clientNotificationList = this._clientNotificationRepository?.FindBy(predicate).Select(x => x.Name).ToList();
//**Form Expression Query Ends**
}
return clientNotificationList;
}
If non of the approaches were good please suggest me the right way to do.
I noticed the clientId being used on both approaches is used on conditions linked to clientNotificationsExists but there is no actual relationship with its objects so this condition can be moved up one level. This might bring a minuscule benefit as the overall sql will be smaller without the duplicate clauses.
From the described behavior the first approach filter is not being translated to sql so it is being resolved at the client side however, if it could be translated the performance would be similar or identical.
If you want to generate an IN sql clause you can use an array and use the Contains method within your filter expression. That could perhaps improve a little bit but don't get your hopes too high.
Try the following:
private bool GetClientNotifications(int clientId, IList<ClientNotification> clientNotifications)
{
IList<string> clientNotificationList = null;
var clientNotificationsExists = clientNotifications?
.Select(x => new { x.Name, x.notificationId }).ToList();
if (clientNotificationsExists?.Count > 0)
{
var notificationIds = clientNotificationsExists.Select(x => x.notificationId).ToArray();
clientNotificationList = this._clientNotificationRepository?
.FindBy(x => x.clientId == clientId && notificationIds.Contains(x.notificationId));
}
return clientNotificationList;
}

Lambda expression - For a Select New

I am trying to create a custom collection from an IQueryable object, where i am trying to perform a select statement but getting an error cannot convert to store expression. I am new to Lambda Expression. Kindly help me how to fix this problem.
Getting error at line c.Event.FirstUpper()
public static string FirstCharToUpper(string input)
{
if (String.IsNullOrEmpty(input))
return string.Empty;
var trimmed = input.Trim();
return trimmed.First().ToString().ToUpper() + trimmed.Substring(1);
}
public static Expression<Func<string, string>> GetFirstCaseToUpperExpression()
{
var expression = NJection.LambdaConverter.Fluent.Lambda.TransformMethodTo<Func<string, string>>()
.From(() => StringFormatter.FirstCharToUpper)
.ToLambda();
return expression;
}
Calling the Expression
return new List<LoggerModel>(
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode)).OrderByField(sortBy, ascendingOrder).Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
I suppose you are using Entity Framework or a smiliar O/R mapper.
Think about what you are doing here: you are writing a LINQ query that should be executed against your database. To do this, it will translate your LINQ query into a SQL query which will then be executed against your database.
But FirstCharToUpper() is a custom method in your code. Your database does not know anything about it, so your O/R mapper's LINQ provider cannot translate it into anything meaningful in SQL, hence you get the error.
So what you need to do is to first "finish" the query against your database to have the results in-memory and after that, apply any further processing that can only be done within the boundaries of your code on that in-memory collection.
You can do this simply by inserting .AsEnumerable() in your LINQ query before you do the select with your custom expression:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.AsEnumerable()
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
When calling AsEnumerable(), the query against your database will be executed and the results are copied into an IEnumerable in memory. The Select() afterwards will now already be executed against the in-memory collection and not against the database anymore, thus it can use your custom FirstCharToUpper() method.
Edit based on your comments below:
Everything above is still valid, but in the comments you said your function needs to return IQueryable. In your case, what your FirstCharToUpper() method is doing is pretty simple and the LINQ-to-Entities provider does support methods like ToUpper and Substring. So I'd recommend to simply get rid of your helper method and instead write your LINQ query to do just that with methods that Entity Framework can translate to valid SQL:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.Substring(0, 1).ToUpper()
+ c.Event.Substring(1)
})
This will result in a SQL query that will already return the content in Event with an uppercase first letter right from the database.
To also support the IsNullOrEmpty check and the Trim you are doing (both also supported by LINQ-to-Entities) I recommend to change the lambda syntax to the LINQ query syntax so you can use the let statement for the trimming, which makes the code cleaner:
from c in logDB.PELoggers
let trimmedEvent = c.Event.Trim()
where c.SubscriberCode == SubscriberCode
select new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = !string.IsNullOrEmpty(trimmedEvent)
? trimmedEvent.Substring(0, 1).ToUpper()
+ trimmedEvent.Substring(1)
: string.Empty
};
In case you do not want to have this done in the LINQ query, you would need to do the uppercasing at some point later when your query against the DB has been executed, for example right in the View that will show your data. Or one option could be to apply the uppercasing in the Event property setter of your LoggerModel:
public class LoggerModel
{
// ...
private string event;
public string Event
{
get { return event; }
set { event = FirstCharToUpper(value); }
}
// ...
}
But there is no way to make custom functions work inside LINQ-to-Entities queries.

C# NET LINQ to Entities does not recognize the method

I'm getting this error when trying to do a linq query:
LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable`1[FEI.Entities.EF6.Tables.HORSE_IDENTITY_GENDER] GetHorseIdentityGenderQuery(FEI.Entities.EF6.Tables.HORSE_DOCUMENT_PART)' method, and this
method cannot be translated into a store expression.
I've read a lots of previous questions where people get the same error, but I understand that it's because LINQ to Entities requires the whole linq query expression to be translated to a server query, and therefore you can't call an outside method in it. I haven't been able to convert my scenario into something that works yet, and my brain is starting to melt down, so I was hoping someone could point me in the right direction. We're using Entity Framework and the specification pattern (and I'm new to both).
Here's the code that uses the specification:
HORSE_DOCUMENT HorseDocForPart = Bll.GetHorseDocumentForPartUpload(horseId, identityType);
Here's the code that provides from method GetHorseDocumentForPartUpload
public HORSE_DOCUMENT GetHorseDocumentForPartUpload(int horseID, HorseDocGender identityType)
{
// Get the unique horse document
var horseDoc = Tables
.HORSE_DOCUMENT
.Where(hd =>
hd.HORSE_UID == horseID &&
hd.HORSE_IDENTITY_TYPE.HORSE_IDENTITY_TYPE_FULL_CODE == identityType.ToString() &&
!hd
.HORSE_DOCUMENT_PART
.Any(hdp =>
hdp.VALIDATION_STATUS != HorseDocPartStatus.REFUSED.ToString() &&
GetHorseIdentityGenderQuery(hdp).Any(hig => hig.IS_FULL)
)
).SingleOrDefault();
return horseDoc;
}
Here's the last code :
public IEnumerable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(HORSE_DOCUMENT_PART horseDocPart)
{
var possibleDocs = Tables
.DOCUMENTs
.Where(doc => doc.DOC_OWNER_UID == horseDocPart.HORSE_DOCUMENT_PART_UID);
return horseDocPart
.HORSE_DOCUMENT
.HORSE_IDENTITY_TYPE
.HORSE_IDENTITY_GENDER
.Join(
possibleDocs,
hig => hig.DOCUMENT_GENDER_CODE.DOCUMENT_GENDER_CODE_UID,
doc => doc.DOCUMENT_GENDER_CODE_UID,
(dgc, doc) => dgc
);
}
You return IEnumerable from the method
public IEnumerable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(...)
This is deferred but using IEnumerable does not allow Linq-To-Sql execution, you should be using IQueryable as such.
public IQueryable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(...)
Please see more detailed explanation from Returning IEnumerable<T> vs. IQueryable<T>

ToUpper() in lambda not working

var Result = addressContext.Address_Lookup
.Where(c => c.Address_Full.ToUpper().Contains(term.ToUpper())
|| c.Address_Full.ToUpper().Contains(TermModified.ToUpper()))
.Select(e => new {
id = e.Address_ID,
label = e.Address_Full,
value = e.Address_Full })
.ToList();
To ensure search will be non-case sensitive I am using ToUpper().
I am searching for something like Jimmy (with a capital J). jimmy (all lower case) doesnt catch? why?
Since you're using entity-framework, a linq-to-sql framework, you're actually trying to make the database perform a .ToUpper rather than performing one in-memory as you would if running through an IEnumerable. If the query translation in your framework doesn't support the function, it either won't be used or throw an Exception.
You can generally predict such behaviour by checking whether you're calling a function against an IQueryable object, which queues all calls as an expression tree for translation, or an IEnumerable, which uses foreach and yield returns to handle evaluation. Since the Linq functions are extension methods, polymorphism doesn't apply here.
If you're not worried about the performance hit of getting EVERY entry from that table in-memory, add a .AsEnumerable() call, and your functionwill evaluate on localized data.
var Result = addressContext.Address_Lookup
.AsEnumerable()
.Where(c => c.Address_Full.ToUpper().Contains(term.ToUpper())
|| c.Address_Full.ToUpper().Contains(TermModified.ToUpper()))
.Select(e => new
{
id = e.Address_ID,
label = e.Address_Full,
value = e.Address_Full
})
.ToList();

Can't get deferred LINQ statements to work in nHibernate

Trying to write dynamic queries using the LINQ provider for NHibernate, but I am having issues. My understanding was that LINQ queries were deferred until called, (i.e. with ToList()), so I have the following code:
string[] filteredIds = new[] { "someIdNotInUse"};
var result = _products
.GetAll()
.Skip(0)
.Take(10);
if (filteredIds != null)
{
result.Where(x => x.Child1.Child2.Any(z => filteredIds.Contains(z.Child3.Id)));
}
var r = result.ToList();
The Where filter in the conditional block is not applied; when I run .ToList, I get records where I expect none. However, if I remove the where filter and append it directly to the _products call, it works as expected. Am I misunderstanding how the LINQ provider works? How is creating a query like this possible, without rewriting the query for every possible filter condition and combination?
Methods in LINQ don't affect the object they're called on - they return a new object representing the result of the call. So you want:
if (filteredIds != null)
{
result = result.Where(...);
}
(Think of it as being a bit like calling Replace or Trim on a string - the string is immutable, so it's only the return value which matters.)

Categories