Entity Framework and Repository Pattern (problem with IQueryable) - c#

I just switched from Linq 2 SQL to Entity Framework, and I'm seeing some strange behaviors in EF that I'm hoping someone can help with. I tried Googling around, but I wasn't able to find other people with this same problem. I've mocked up a scenario to explain the situation.
If I work directly with an EF context, I'm able to do a select within a select. For example, this executes perfectly fine:
// this is an Entity Framework context that inherits from ObjectContext
var dc = new MyContext();
var companies1 = (from c in dc.Companies
select new {
Company = c,
UserCount = (from u in dc.CompanyUsers
where u.CompanyId == c.Id
select u).Count()
}).ToList();
However, if I use a repository pattern where the repository is returning IQueryable (or even ObjectSet or ObjectQuery), I get a NotSupportedException (LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1)...
Here is an example of my repository:
public class Repository {
private MyContext _dc;
public Repository() {
_dc = new MyContext();
}
public IQueryable<Company> GetCompanies() {
return _dc.Companies;
}
public IQueryable<CompanyUser> GetCompanyUsers() {
return _dc.CompanyUsers;
}
}
// I'm using the repository inside another class (e.g. in my Services layer)
var repository = new Repository();
var companies2 = (from c in repository.GetCompanies()
select new {
Company = c,
UserCount = (from u in repository.GetCompanyUsers()
where u.CompanyId == c.Id
select u).Count()
}).ToList();
The above code throws a NotSupportedException.
I realize that if there's an association between Companies and CompanyUsers, then I can simply do this and it will work fine:
var companies3 = (from c in repository.GetCompanies()
select new {
Company = c,
UserCount = (from u in c.CompanyUsers
select u).Count()
}).ToList();
...but my example is just a simplified version of a more complicated scenario where I don't have an association between the entities.
So I'm very confused why Entity Framework is throwing the NotSupportedException. How is it that the query works perfectly fine when I'm working with the EF context directly, but it's not supported if I'm working with IQueryable returned from another method. This worked perfectly fine with Linq 2 SQL, but it doesn't seem to work in Entity Framework.
Any insight would be greatly appreciated.
Thanks in advance.

I suspect that what's happening is that EF sees the expression for repository.GetCompanyUsers() inside the lambda for the first select and doesn't know what to do with it because repository isn't an EF context. I think that if you pass in the IQueryable directly instead of an expression that returns it, it should work.
How about if you do this:
var companyUsers = repository.GetCompanyUsers();
var companies2 = (from c in repository.GetCompanies()
select new {
Company = c,
UserCount = (from u in companyUsers
where u.CompanyId == c.Id
select u).Count()
}).ToList();

This is one of those strange quirks with Linq to SQL/EF. Apparently they implemented a way to translate from a getter property to SQL, but not a way to translate from a getter function to SQL.
If instead of a function GetCompanyUsers() you use a property like CompanyUsers, it should work.
Weird eh?
So instead of
public IQueryable<CompanyUser> GetCompanyUsers() {
return _dc.CompanyUsers;
}
You might do
public IQueryable<CompanyUser> CompanyUsers {
get { return _dc.CompanyUsers; }
}
As far as parameterized queries go (which you can't do with a property, obviously), see my question answered here: Custom function in Entity Framework query sometimes translates properly, sometimes doesn't
You can also have wheres and selects in the property too; they'll translate fine. For instance, if I had a blog with an Articles table that contains some articles that aren't online:
public IQueryable<Article> LiveArticles {
get { return _dc.Articles.Where(a => !a.IsDraft); }
}
That'll also reduce the number of parameterized queries that you need.

Related

How to implement a generic method in Repository to make joins in linq

Hello I have a linq query that makes several joins on tables and which eventually returns me a single row.
For the time being, I have used the generic method GetAll() that returns the single row.
The query that I have written is as follows:
from h in _repositoryWorkers.GetAll()
join p in _repositoryJobs.GetAll() on h.Id equals p.THI_N_ID
join q in _repositoryHome.GetAll() on h.Id equals q.THI_N_ID
public Repository()
{
this.context = new WorkersContext();
entities = context.Set<T>();
}
public IEnumerable<T> GetAll()
{
return entities.AsEnumerable<T>();
}
What I want to achieve is the following as an example:
from w in repo.Query<Worker>()
join e in repo.Query<XEntity>() on ...
I do not want to use a List that returns me only a single row. I just want to return an object of this row.
Please advise.
In isolation, I would write something like:
var result = context.Worker()
.Include(worker => worker.XEntity)
.Where(worker => worker.ID == id)
.SingleOrDefault();
result will either be the single Worker entity with XEntity populated or null if there is no entry with a matching id.
How do you make it generic though?
I've tried a lot of things over the last few years and in the end, I find that specific methods work best.
So I'll have a generic base repository for the simple stuff but then I'll extend that when I need to do things like this.
So I would have a specific repository for the Worker entity that extends the generic base.
It would have a method like:
public Worker GetByIdWithXEntity(id)
{
this.Queryable()
.Include(worker => worker.XEntity)
.Where(worker => worker.ID == id)
.SingleOrDefault();
}
(Queryable is a protected method on the base repository)
This approach is a bit of a pita with large projects but I've found that it works very nicely as it keeps all the data access logic in the Repo, it makes unit testing your services simple and it's very clear exactly what's going on.
That said, if anyone has a better way, I'd love to hear it!

Convert Linq statement to be used in abstracted repository pattern

This Linq statement works perfectly except that I realized that I am required to use the repository and unit of work ...
So I have this Linq query
var query = (from rg in ReportGroups
join rd in ReportDefinitions on rg.ReportGroupID equals rd.ReportGroupID
where rd.ReportGroupID == 5
select rg).Count();
What I am seeing is that
ReportGroups uses GetReportGroups ( for GetAll() )
ReportDefinitions uses GetReportDefinitions (for GetAll() )
So an example is
object responseObject = null;
responseObject = _reportService.GetReportGroups("en-us").ToList();
responseObject = _reportService.GetReportDefinitions("en-us").Where(e => e.ReportGroupID == Convert.ToInt32(id)).ToList();
So you can see that is how I'm currently having to retrieve data.
I am wanting to do a Join, but i'm not sure how I can do this.
I was thinking about multiple calls, many a for loop...
Also I noticed how an existing service is called in a different method with lambda statement
private IEnumerable<SelectListItem> GetActivityList()
{
return _activityService
.GetAllActivities()
.OrderBy(n => n.ActivityName)
.Select(a => new SelectListItem
{
Text = a.ActivityName,
Value = a.Id.ToString(CultureInfo.InvariantCulture)
});
}
I realize that people cannot see all the underlying data abstractions, and I can certainly provide anything asked, I'm just a bit stumped as I used Linqpad to connect to the database and wrote my query, but now I realize that the join etc.. is maybe not so easy with the service layers.
return DbSet.Include(x => x.ReportGroups )
.Include(x => x.ReportDefinitions ).Where(//id condition);

Apply a custom method on EntityFramework results

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.

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.

Can someone explain why these two linq queries return different results?

I have two linq (to EF4) queries, which return different results. The first query contains the correct results, but is not formatted/projected right.
the second query is what i want but it missing some data.
Schema
alt text http://img220.imageshack.us/img220/9678/schema.png
Query 1
var xxxx = (from cp in _connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
select cp)
.ToList();
alt text http://img231.imageshack.us/img231/6541/image2ys.png
Notice the property GameFile . It is not null. This is great :) Notice the linq query? I'm eager loading a LogEntry and then eager loading a GameFile (for each eager loaded LogEntry).
This is what i'm after -> for each LogEntry that is eager loaded, please eager load the GameFile. But this projection result is wrong...
Ok.. next...
Query 2
var yyy = (from cp in _connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
select cp.LogEntry)
.ToList();
NOTE: the image above has a typo in it ... please note the include associations typed code is correct (ie. LogEntry.GameFile) while the image has it typo'd.
Correct projection now -> all LogEntries results. But notice how the GameFile property is now null? I'm not sure why :( I thought i correctly eager loaded the correct chain. So this is the correct projection but with incorrect results.
Obligatory Repository code.
public IQueryable<ConnectedClient> GetConnectedClients(
string[] includeAssociations)
{
return Context.ConnectedClients
.IncludeAssociations(includeAssociations)
.AsQueryable();
}
public static class Extensions
{
public static IQueryable<T> IncludeAssociation<T>(
this IQueryable<T> source, string includeAssociation)
{
if (!string.IsNullOrEmpty(includeAssociation))
{
var objectQuery = source as ObjectQuery<T>;
if (objectQuery != null)
{
return objectQuery.Include(includeAssociation);
}
}
return source;
}
public static IQueryable<T> IncludeAssociations<T>(
this IQueryable<T> source, params string[] includeAssociations)
{
if (includeAssociations != null)
{
foreach (string association in includeAssociations)
{
source = source.IncludeAssociation(association);
}
}
return source;
}
}
Updates
1 : Fixed some typo's in noticed in the code samples.
2 : Added repository code to help anyone who is confused.
I suspect Craig Stuntz' suggestion may work, but if it doesn't, the following should certainly work:
var xxxx =_connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
.ToList() // execute query
.Select(cp => cp.LogEntry); // use linq-to-objects to project the result
Include() works on the query results, rather than the intermediate queries. You can read more about Include() in this post. So one solution is to apply the Include() to the whole query, like this:
var q = ((from cp in _connectedClientRepository.GetConnectedClients()
.AsExpandable()
.Where(predicate)
select cp.LogEntry)
as ObjectQuery).Include("GameFile").ToList();
That will probably work, but it's ugly. Can we do better?
I can think of two ways around this issue. Mostly, it depends upon whether or not you actually need entity types returned. I can't say whether this is the case without seeing the rest of your code. Generally, you need to return entity types when you are going to update (or otherwise modify) them. If you are selecting for display or calculation purposes, it's often a better strategy to return POCOs instead of entity types. You can do this with projection, and of course it works in EF 1. In this case, you would change your repository method to return POCO types:
public IQueryable<ClientInfo> GetConnectedClients()
{
return from cp in _context.Clients
where // ...
select new ClientInfo
{
Id = cp.Id,
ClientName = cp.ClientName,
LogEntry = new LogEntryInfo
{
LogEntryId = cp.LogEntry.LogEntryId,
GameFile = new GameFileInfo
{
GameFileId = cp.LogEntry.GameFile.GameFileId,
// etc.
},
// etc.
},
// etc.
};
}
Note that when you use projection there is no eager loading, no lazy loading, and no explicit loading. There is only your intention, expressed as a query. The LINQ provider will figure out what you need, even if you further compose the query outside the repository!
On the other hand, you might need to return entity types instead of POCOs, because you intend to update them. In that case, I would write a separate repository method for the LogEntries, as Tomas suggested. But I would only do this if I intended to update them, and I might write it as an update method, rather than a "Get" method.
It seems you need one more repository method, that will do this for you; _connectedClientsRepository.GetLogEntriesOfConnectedClients().

Categories