LINQ to Entities does not recognize the method IsUserInCc - c#

My Code:
elections = elections.Where(e => e.Creator == Username || e.Approver == Username || IsUserInCc(e.Cc,Username))
.OrderBy(e => e.Status)
.ThenByDescending(e => e.Group);
var test = elections.FirstOrDefault();
private bool IsUserInCc(string cc, string username)
{
var ccList = cc.Split(';');
if (ccList.Contains(username))
return true;
return LDAPUtility.Instance.IsUserInGroup(ccList.ToList(), username);
}
Error:
LINQ to Entities does not recognize method IsUserInCc.
From many posts, I can understand why error was thrown. Basically IsUserInCc is not available in SQL execution. I need somehow convert it back to C# to handle it.
LINQ to Entities does not recognize my method
LINQ to Entities does not recognize the method in query
LINQ to Entities does not recognize the method 'System.String ToString(Int32)'
However, in my specific case, what is the best approach?

You need to convert to list first. Also note that elections must be able to hold a list for this to run.
elections = elections.ToList().Where(e => e.Creator == Username || e.Approver == Username || IsUserInCc(e.Cc,Username))
.OrderBy(e => e.Status)
.ThenByDescending(e => e.Group);

For your function written in code, you cannot use that on Queryables. You need to convert to in-memory list and then apply the filter required using your function.

The root cause of your issue is that your underlying data isn't normalised properly. You need to put your CC's in a collection, not have them as a single deliniated string.
In SQL you'd need to add a new table called CC or something and put each user name in there and link it back to an election. Or if it's an in-memory collection, add a new property that in its Getter will do the split for you.
Either way, then you won't run into this kind of problem. If your data isn't properly structured, you will create problems for yourself further up the stack.

When you want to send request to databaseusing Linq like:
var query = listData.Where(x=>x.Id == 123);
Type of this query is IQueryable that means your query not Executed yet!
Now you are sending data as IQueryable to method and can not process on your data, you have to Execute that with methods like: Tolist(), ToListAsync() or something like these.
The best way for these is that you get data from database without that method, after that you execute your query, you can Run this method.
GoodLuck.

Can you try like this :
elections = elections.Where(e => e.Creator == Username || e.Approver == Username).Tolist().Where(e => IsUserInCc(e.Cc,Username))
.OrderBy(e => e.Status)
.ThenByDescending(e => e.Group);
var test = elections.FirstOrDefault();
private bool IsUserInCc(string cc, string username)
{
var ccList = cc.Split(';');
if (ccList.Contains(username))
return true;
return LDAPUtility.Instance.IsUserInGroup(ccList.ToList(), username);
}

Related

How to make a linq-query with multiple Contains()/Any() on possibly empty lists?

I am trying to make a query to a database view based on earlier user-choices. The choices are stored in lists of objects.
What I want to achieve is for a record to be added to the reportViewList if the stated value exists in one of the lists, but if for example the clientList is empty the query should overlook this statement and add all clients in the selected date-range. The user-choices are stored in temporary lists of objects.
The first condition is a time-range, this works fine. I understand why my current solution does not work, but I can not seem to wrap my head around how to fix it. This example works when both a client and a product is chosen. When the lists are empty the reportViewList is obviously also empty.
I have played with the idea of adding all the records in the date-range and then removing the ones that does not fit, but this would be a bad solution and not efficient.
Any help or feedback is much appreciated.
List<ReportView> reportViews = new List<ReportView>();
using(var dbc = new localContext())
{
reportViewList = dbc.ReportViews.AsEnumerable()
.Where(x => x.OrderDateTime >= from && x.OrderDateTime <= to)
.Where(y => clientList.Any(x2 => x2.Id == y.ClientId)
.Where(z => productList.Any(x3 => x3.Id == z.ProductId)).ToList();
}
You should not call AsEnumerable() before you have added eeverything to your query. Calling AsEnumerable() here will cause your complete data to be loaded in memory and then be filtered in your application.
Without AsEnumerable() and before calling calling ToList() (Better call ToListAsync()), you are working with an IQueryable<ReportView>. You can easily compose it and just call ToList() on your final query.
Entity Framework will then examinate your IQueryable<ReportView> and generate an SQL expression out of it.
For your problem, you just need to check if the user has selected any filters and only add them to the query if they are present.
using var dbc = new localContext();
var reportViewQuery = dbc.ReportViews.AsQueryable(); // You could also write IQuryable<ReportView> reportViewQuery = dbc.ReportViews; but I prefer it this way as it is a little more save when you are refactoring.
// Assuming from and to are nullable and are null if the user has not selected them.
if (from.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime >= from);
if (to.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime <= to);
if(clientList is not null && clientList.Any())
{
var clientIds = clientList.Select(c => c.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => clientIds.Contains(y.ClientId));
}
if(productList is not null && productList.Any())
{
var productIds = productList.Select(p => p.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => productIds .Contains(r.ProductId));
}
var reportViews = await reportViewQuery.ToListAsync(); // You can also use ToList(), if you absolutely must, but I would not recommend it as it will block your current thread.

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.

Where clause with Linq in C# MVC 4

I'm trying to select a field that is from my UserProfile table, RoleID. The parameter passed into this Post method is Username and it is a string from a textbox which is working correctly.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult GetRoles(string UserName)
{
if (!string.IsNullOrWhiteSpace(UserName))
{
string applyfor = db.Profiles
.Select(s => s.RoleID)
.Where(a=>Profile.UserName.Contains(UserName))
.First();
ViewBag.ApplyingFor = applyfor;
However this gives me Sequence contains no elements.
I've tried several other methods, such as .Equals(). I'm pretty sure it is my where clause.
What am I doing wrong here?
Note: RoleID is not part of the Websecurity, also there is data in the database.
If you break down your code and highlight what each Lambda statement returns you'd see the issue:
string applyfor = db.Profiles
^^^^^^^^
This most likely returns something like DbSet<Profile>.
.Select(s => s.RoleID)
^^^^^^
This most likely returns IQueryable<int>. At this point you've lost the context of the profile and now only have zero or more RoleIDs.
So your a in the where statement is an int Value, you have no way to find a username now, and this where statement literally makes no sense.
.Where(a=>Profile.UserName.Contains(UserName))
When you rearrange the Lambda expressions as Grant Winney's Answer shows you can see why most of the time a Select() is the last thing that normally happens (in simple queries).
I would wager there is no UserName on Profile. and you want to
string applyfor = db.Profiles
.Where(p => p.User.Any(u.UserName == UserName))
.Select(p => p.RoleID)
.First();
As a side note, Microsoft Best practice is to Camel-Case method parameters. So I would recommend your method look like:
public ActionResult GetRoles(string userName) // or username
{
}
Your Where statement should probably look more like this:
... .Where(a => a.UserName == Profile.UserName).FirstOrDefault();
Try this instead:
string applyfor = db.Profiles
.Where(x => x.UserName == UserName)
.Select(x => x.RoleID)
.First();
Also, if there's a chance you won't find a matching record, use FirstOrDefault() instead of .First() and then test for null.

Is it possible to format DB content (as string) before comparison with where clause

I'd like to be able to take a url-formatted string (e.g united-kingdom) and use it in a WHERE clause against a Country column that is not formatted in such a way (e.g. United Kingdom).
Ideally, I'd like to be able to do something like this:
db.Jobs
.Where(j => j.Country.MyStringFormattingExtension() == urlformattedstring);
I understand that this is a no go because EF would try and execute the projection on the SQL side. It gives me: "LINQ to Entities does not recognize the method 'System.String MyStringFormattingExtension(System.String)' method, and this method cannot be translated into a store expression."
It has been suggested that I return the query as an enumerable before applying the where clause, however I think that this would be pretty inefficient - returning all rows from the DB before filtering.
You can define a user defined function and import that into your database. Read this article for more details
// In SQL
CREATE FUNCTION dbo.ToFormattedString ...
// In C#
public static class EntityFunctions
{
[EdmFunction("dbo", "ToFormattedString")]
public static string ToFormattedString(this string input)
{
throw new NotSupportedException("Direct calls not supported");
}
}
var results = db.Jobs.Where(j => j.Country.ToFormattedString() == urlFormattedString);
Alternatively, you can create a view in your database that materializes the string the way you want it to be formatted then join it into your Linq query.
// In SQL
CREATE VIEW dbo.vFormattedJobs AS ...
// In C#
var results =
(from j in db.vFormattedJobs
where j.FormattedCountry == urlFormattedString
select j);
How about going the other way, and converting the URL-formatted string into the database format before using it in the query?
var dbFormattedString = urlformattedstring.ConvertToDbFormat();
var result = db.Jobs.Where(j => j.Country == dbFormattedString);
(db.Jobs is already an IEnumerable, so I suppose that the suggestion was to call ToList() on it - it would have worked, but indeed, it would have been very inefficient unless the table is very small.)

How to structure a partially dynamic LINQ statement?

I currently have a method on my repository like this:
public int GetMessageCountBy_Username(string username, bool sent)
{
var query = _dataContext.Messages.AsQueryable();
if (sent)
query = query.Where(x => x.Sender.ToLower() == username.ToLower());
else
query = query.Where(x => x.Recipient.ToLower() == username.ToLower());
return query.Count();
}
It currently builds one of two queries based on the sent boolean. Is this the best way to do this or is there a way to do this within the query itself? I want to check if x.Sender is equal to username if sent equals true. But I want to check if x.Recipient is equal to username if sent equals false.
I then want this LINQ expression to translate into SQL within Entity Framework, which I believe it is doing.
I just want to avoid repeating as much code as possible.
You could do something like this :
public int GetMessageCountBy_Username(string username, bool sent)
{
Func<Message, string> userSelector = m => sent ? m.Sender : m.Recipient;
var query =
_dataContext.Messages.AsQueryable()
.Where(x => userSelector(x).ToLower() == username.ToLower());
return query.Count();
}
Thus the choosing of the right user (the sender or the recipient) is done before the linq part, saving you from repeating it twice.
Yes, I believe this is correct way to do it. Because it is easy to create complex queries without repeating whole parts of queries.
And your thinking about translating to SQL is correct too. But beware, this is done at the moment, when data or agregation is requested. In your case, the SQL will be generated and executed when you call Count().

Categories