Apply a custom method on EntityFramework results - c#

Is there a way to do something like this?
using (var db = new MyEntities())
{
// getting all users with EntityFramework 6
var allUsers = from u in db.Users
select new
{
MyCustomMethod(u.FirstName)
};
}
Of course, because EF6 cannot translate my method to an SQL query it throws an exception. Then what I need to do was to create another loop on that result and run my method on the member, is there a way to not be needing that second loop?

If you do not have any problem with linq method chain style you can do something like this,
db.Users.AsEnumerable().Select(method);
And write some method like this,
public string method(User a)
{
return a.UserName;
}

Well I'm afraid you can't.
Unless you can use one of the SqlFunctions EF provides, you have to build your own expression tree, and I guess it won't be possible if the role of MyCustomMethod is to "deserializes an byte[] to and object".

You can do this:
var databaseUsers = from u in db.Users
/* add clauses to execute in the database here */
select new { u.FirstName };
var allUsers = from u in databaseUsers.AsEnumerable()
select new
{
MyCustomMethod(u.FirstName)
};
The .AsEnumerable() call essentially tells EF that what happens after the call should not be translated into database logic. It doesn't execute any code. It just changes the type of the expression from an IQueriable<T> into an IEnumerable<T>.
See the AsEnumerable documentation for more information.

Related

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.

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.)

linq join on guid and string column

I'm new to linq. I need to run a query that joins two columns (AnonymousUser.AnonymousId being uniqueidentifier and comment.UserId being nvarchar(100)), something like below:
using (CommentEntities db = new CommentEntities())
{
// filteredComments is a query that is not run until the next .ToList()
IQueryable<Comment> filteredComments = this.CommentGetList(...);
var query = from comment in filteredComments
// following line is the syntax error, because columns' types don't match
join user in db.AnonymousUsers on comment.UserId equals user.AnonymousId into gj
from userNull in gj.DefaultIfEmpty()
select new CommentWithName
{
Comment = comment,
UserId = comment.UserId,
FirstName = (userNull == null ? "" : userNull.Name),
LastName = "",
Email = (userNull == null ? "" : userNull.Email)
};
return query.ToList();
}
First I was happy writing the query with .ToString() ! As it turns out that entity framework doesn't know how to translate it to sql. The same is true for Guid.Parse(string). Also new Guid(string) cannot be used in linq to entities (only parameterless constructors allowed)!
So after searching, I found out it's not possible doing such thing in EF 4.0! I migrated my code to a stored procedure that I'm not really happy about it.
Is it possible to tell entity framework to use a CAST in SQL?
Is there any solutions to this problem? Is there any way that I can bring the logic in code?
NOTE: I meant to do it in one GO. Otherwise one possible solution is to get Entities from first table, and put the Ids in a list and get entities from second table.
call toList() before applying those methods. Like:
var Product = db.Products.Where(p => p.ProductId == Guid.Parse("B4E913F9-166C-49BA-AADE-6DB889D1756F")).Single();
Would throw a
c# LINQ to Entities does not recognize the method "System.Guid Parse" (System.String)' method, and this method cannot be translated into a store expression
But this works:
var Product = db.Products.ToList().Where(p => p.ProductId == Guid.Parse("B4E913F9-166C-49BA-AADE-6DB889D1756F")).Single()
p.s.: I think you will lose lazyloading but you can do eagerloading with .Include before calling .ToList().
If your list is object list you could convert it to the type which has Guid as identifier, first create new anonymous type and then filter it base on UserId, sure UserId which is of type int, wont include in join:
int output = 0;
var secondList = list.Where(x=>!int.TryParse(x.UserID, out output))
.Select(x=>new {Comment = x, ID = new Guid(x.UserID))
.ToList();
Now you could run your query on db by using secondList.

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression

I'm migrating some stuff from one mysql server to a sql server but i can't figure out how to make this code work:
using (var context = new Context())
{
...
foreach (var item in collection)
{
IQueryable<entity> pages = from p in context.pages
where p.Serial == item.Key.ToString()
select p;
foreach (var page in pages)
{
DataManager.AddPageToDocument(page, item.Value);
}
}
Console.WriteLine("Done!");
Console.Read();
}
When it enters into the second foreach (var page in pages) it throws an exception saying:
LINQ to Entities does not recognize the method 'System.String
ToString()' method, and this method cannot be translated into a store
expression.
Anyone know why this happens?
Just save the string to a temp variable and then use that in your expression:
var strItem = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == strItem
select p;
The problem arises because ToString() isn't really executed, it is turned into a MethodGroup and then parsed and translated to SQL. Since there is no ToString() equivalent, the expression fails.
Note:
Make sure you also check out Alex's answer regarding the SqlFunctions helper class that was added later. In many cases it can eliminate the need for the temporary variable.
As others have answered, this breaks because .ToString fails to translate to relevant SQL on the way into the database.
However, Microsoft provides the SqlFunctions class that is a collection of methods that can be used in situations like this.
For this case, what you are looking for here is SqlFunctions.StringConvert:
from p in context.pages
where p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;
Good when the solution with temporary variables is not desirable for whatever reasons.
Similar to SqlFunctions you also have the EntityFunctions (with EF6 obsoleted by DbFunctions) that provides a different set of functions that also are data source agnostic (not limited to e.g. SQL).
The problem is that you are calling ToString in a LINQ to Entities query. That means the parser is trying to convert the ToString call into its equivalent SQL (which isn't possible...hence the exception).
All you have to do is move the ToString call to a separate line:
var keyString = item.Key.ToString();
var pages = from p in context.entities
where p.Serial == keyString
select p;
Cast table to Enumerable, then you call LINQ methods with using ToString() method inside:
var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)
But be careful, when you calling AsEnumerable or ToList methods because you will request all data from all entity before this method. In my case above I read all table_name rows by one request.
Had a similar problem.
Solved it by calling ToList() on the entity collection and querying the list.
If the collection is small this is an option.
IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())
Hope this helps.
Upgrading to Entity Framework Version 6.2.0 worked for me.
I was previously on Version 6.0.0.
Hope this helps,
Change it like this and it should work:
var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == key
select p;
The reason why the exception is not thrown in the line the LINQ query is declared but in the line of the foreach is the deferred execution feature, i.e. the LINQ query is not executed until you try to access the result. And this happens in the foreach and not earlier.
If you really want to type ToString inside your query, you could write an expression tree visitor that rewrites the call to ToString with a call to the appropriate StringConvert function:
using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;
namespace ToStringRewriting {
class ToStringRewriter : ExpressionVisitor {
static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
.Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));
protected override Expression VisitMethodCall(MethodCallExpression node) {
var method = node.Method;
if (method.Name=="ToString") {
if (node.Object.GetType() == typeof(string)) { return node.Object; }
node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
}
return base.VisitMethodCall(node);
}
}
class Person {
string Name { get; set; }
long SocialSecurityNumber { get; set; }
}
class Program {
void Main() {
Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
var rewriter = new ToStringRewriter();
var finalExpression = rewriter.Visit(expr);
var dcx = new MyDataContext();
var query = dcx.Persons.Where(finalExpression);
}
}
}
In MVC, assume you are searching record(s) based on your requirement or information.
It is working properly.
[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{
EmployeeContext employeeContext = new EmployeeContext();
string searchby=formcollection["SearchBy"];
string value=formcollection["Value"];
if (formcollection["SearchBy"] == "Gender")
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
return View("Index", emplist);
}
else
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
return View("Index", emplist);
}
}
I got the same error in this case:
var result = Db.SystemLog
.Where(log =>
eventTypeValues.Contains(log.EventType)
&& (
search.Contains(log.Id.ToString())
|| log.Message.Contains(search)
|| log.PayLoad.Contains(search)
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
)
)
.OrderByDescending(log => log.Id)
.Select(r => r);
After spending way too much time debugging, I figured out that error appeared in the logic expression.
The first line search.Contains(log.Id.ToString()) does work fine, but the last line that deals with a DateTime object made it fail miserably:
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
Remove the problematic line and problem solved.
I do not fully understand why, but it seems as ToString() is a LINQ expression for strings, but not for Entities. LINQ for Entities deals with database queries like SQL, and SQL has no notion of ToString(). As such, we can not throw ToString() into a .Where() clause.
But how then does the first line work? Instead of ToString(), SQL have CAST and CONVERT, so my best guess so far is that linq for entities uses that in some simple cases. DateTime objects are not always found to be so simple...
My problem was that I had a 'text' data type for this column (due to a migration from sqlite).
Solution: just change the data type to 'nvarchar()' and regenerate the table.
Then Linq accepts the string comparison.
I am working on retiring Telerik Open Access and replacing it with Entity Framework 4.0. I came across same issue that telerik:GridBoundColumn filtering stopped working.
I find out that its not working only on System.String DataTypes. So I found this thread and solved it by just using .List() at the end of my Linq query as follows:
var x = (from y in db.Tables
orderby y.ColumnId descending
select new
{
y.FileName,
y.FileSource,
y.FileType,
FileDepartment = "Claims"
}).ToList();
Just turn the LINQ to Entity query into a LINQ to Objects query (e.g. call ToArray) anytime you need to use a method call in your LINQ query.

Avoid "Nullable object must have a value." in Linq-To-Sql

I have a method query like this:
public IList<BusinessObject> GetBusinessObject(Guid? filterId)
{
using (var db = new L2SDataContext())
{
var result = from bo in db.BusinessObjects
where (filterId.HasValue)
? bo.Filter == filterId.value
: true
orderby bo.Name
select SqlModelConverters.ConvertBusinessObject(bo);
return result.ToList();
}
}
At runtime, this throws a System.InvalidOperationException: Nullable object must have a value.
Looking at the Debugger, the problem is my Where Clause: Linq To SQL tries to convert the entire thing to SQL, so even if filterId is NULL, it will still try to access filterId.value.
I thought/hoped the C# compiler/CLR would evaluate that where clause as a code block and only send one of the two branches to Linq To SQL, but that's not how it works.
My refactored version works, but is not very elegant:
public IList<BusinessObject> GetBusinessObject(Guid? filterId)
{
using (var db = new L2SDataContext())
{
var temp = from bo in db.BusinessObjects select bo;
if(filterId.HasValue) temp = temp.Where(t => t.Filter == filterId.Value);
var result = from t in temp
orderby t.Name
select SqlModelConverters.ConvertBusinessObject(bo);
return result.ToList();
}
}
I know that Lazy-Evaluation will make sure that only one query is really sent, but having that temp object in there isn't that great really.
Did you try:
where filterId == null || t.Filter == filterId
Your fix is exactly correct. You are effectively trying to build up a query dynamically, based on your function input. It's a good idea to omit the where clause instead of supplying WHERE TRUE anyway. If I were writing this query, I would go with your fixed version myself.
It's not as pretty as using the language keywords, but it's still the right way to approach the query in my opinion.

Categories