EF Core Update The LINQ expression 'x' could not be translated - entity-framework

I updated my .net core 2.2 to 5
I have a error about ef that
System.InvalidOperationException: 'The LINQ expression 'x' could not
be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or
'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.'
public List<CustomerPageModel> GetCustomers(int AccountID)
{
return (from p in context.Customers
join f in context.Patients on p.ID equals f.CustomerID into ps
from t in ps.DefaultIfEmpty()
where p.AccountID == AccountID
select new CustomerPageModel
{
ID = p.ID,
Name = p.Name,
IsActive = p.IsActive,
TC = p.TC,
Surname = p.Surname,
Email = p.Email,
Address = p.Address,
Phone = p.Phone,
Note = p.Note,
AccountID = p.AccountID,
Pats = string.Join(",", ps.Select(x => x.Name)),
PatCount = ps.Count()
})
.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
}
How can I convert that code?

Your problem line is:
Pats = string.Join(",", ps.Select(x => x.Name)),
Specifically, the string.Join method doesn't translate to SQL, so in previous versions of EF, it had to retrieve the data from the database then in-memory preform the string.Join function. Now EF explicitly tells you it can't run that on the database server - this is a breaking change but a design decision that tells you (the developer) that it may not have been running as efficiently as you thought it was...
To "fix" this, and based on your particular code example I'd recommend the following:
Add a pet names array property to your CustomerPageModel:
public string[] PetNames {get;set;}
And turn the Pets property into a readonly calculated string:
public string Pets { get => string.Join(",", PetNames); }
And change the problem line in your LINQ expression to:
PetNames = ps.Select(x => x.Name).ToArray()

I changed (lamb to subquery)
string.Join(",", ps.Select(x => x.Name))
to
string.Join(",", (from y in PatientList where y.CustomerID == p.ID select y.Name).ToArray()),
I made the group later (after tolist)
var test = mylist.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
problem solved

Related

Using string_agg in the many-to-many relation

I have entities like Product(Id, Name) and Keyword(Id, Description), and there is a many-to-many relationship between them.
The essence of my task is the following, I need to do a full-text search on Name and Description columns, using EF CORE 6
I already have some SQL code that works fine.
SELECT a."Id", a."Name" as name, k.txt
FROM "Products" AS a
LEFT JOIN (
SELECT x."ProductsId" as Id, string_agg(y."Description", ' ') as txt
FROM "ProductKeywords" x
JOIN "Keywords" y ON y."Id" = x."KeywordId"
GROUP BY 1
) k ON a."Id" = k.Id
WHERE to_tsvector(concat_ws(' ', a."Name", k.txt))
## to_tsquery('Some text');
And I need to write some LINQ code that will do something similar, but I have a problem with string_agg, and I don't understand how to implement it in LINQ and EF CORE will reflect it correctly
I tried to do the following
var products = _context.Products
.Select(e => new
{
Id = e.Id,
Name = e.Name,
Keywords = string.Join(" ", e.Keywords.Select(q => q.Description))
}).Where(e => EF.Functions.ToTsVector(e.Keywords).Matches("Some text")).ToList();
But I get an error, and it's most likely because of string.Join
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
Got the result, using linq2db
var query = _context.Products.ToLinqToDB()
.LeftJoin(_context.ProductsKeywords.ToLinqToDB().GroupBy(r => r.ProductId).Select(e => new {
Key = e.Key,
Txt = e.StringAggregate(",", t => t.Keyword.Description).ToValue()
}),
(product, productKeyword) => product.Id == productKeyword.Key,
(i, j) => new {
Id = i.Id,
Txt = j.Txt
}).Select(e => new {
Id = e.Id,
Txt = EF.Functions.ToTsVector(e.Txt)
}).Where(w => w.Txt.Matches("Some text"));

EF Core: group by => InvalidOperationException

I'm working on kind of education project (for myself) to learn EF Core -> code first approach and I'm struggling what is wrong with my query. A have two simple entitities: article, and user and I want to group them to get an list of objects that contain list of all user with related articles.
{
var authorRowModels =
from articles in _myContext.Articles
join authors in _myContext.Users on articles.Author.Id equals authors.Id
group new { articles, authors } by authors into authorWithArticles
select new
{
AuthorFirstName = authorWithArticles.Key.FirstName,
AuthorLastName = authorWithArticles.Key.LastName,
Articles = authorWithArticles.Select(x => x.articles)
};
return View(authorRowModels.AsEnumerable().Select(x => new AuthorRowModel
{
AuthorFirstName = x.AuthorFirstName,
AuthorLastName = x.AuthorLastName,
Articles = x.Articles.ToList()
}));
}
but I'm getting an error:
InvalidOperationException: The LINQ expression 'DbSet .LeftJoin( outer: DbSet, inner: a => EF.Property<Nullable>(a, "AuthorId"), outerKeySelector: u0 => EF.Property<Nullable>(u0, "Id"), innerKeySelector: (o, i) => new TransparentIdentifier<Article, User>( Outer = o, Inner = i )) .Join( outer: DbSet, inner: a => a.Inner.Id, outerKeySelector: u => u.Id, innerKeySelector: (a, u) => new TransparentIdentifier<TransparentIdentifier<Article, User>, User>( Outer = a, Inner = u )) .GroupBy( source: ti => ti.Inner, keySelector: ti => new { articles = ti.Outer.Outer, authors = ti.Inner })' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
and I have no idea what EF not able to translate. Also if you have a hint how to rewrite this query in better way maybe even without grouping it'd be great :)
Thank you.
Using join in EF LINQ is almost always the wrong solution.
With a Navigation Property this can simply be something like:
var authorRowModels = from a in _myContext.Users
where a.Articles.Any()
select new
{
a.FirstName,
a.LastName,
a.Articles
};

Why only primitive types or enumeration types are supported in this context using EF?

In my application I try to execute a Join query using EntityFramework, Repository Pattern, It is throwing bellow error. What is the problem in the link query? Let me explain in details
Error Description
Unable to create a constant value of type 'Anonymous type'. Only
primitive types or enumeration types are supported in this context
Initialization
_repository = new GenericRepository<WeeklyEntry>();
_repositoryGroup = new GenericRepository<Group>();
_repositoryGroupMember = new GenericRepository<GroupMember>();
Fetching Logic
var groups = _repositoryGroup.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.ID, s.Name }).ToList();
var groupMembers = _repositoryGroupMember.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.GroupID, s.ID, s.Name })
.ToList();
Main Query [Not Working]
var results = (from we in _repository.GetAll()
join g in groups on we.GroupID equals g.ID into grpjoin
from g in grpjoin.DefaultIfEmpty()
join gm in groupMembers on we.DepositedByMemberID equals gm.ID into gmjoin
from gm in gmjoin.DefaultIfEmpty()
where gm.GroupID == g.ID
select new
{
GroupID = g.ID,
GroupName = g.Name,
MemberID = grpmresult.ID,
grpmresult.Name,
we.ID
}).ToList();
To try to achieve bellow SQL Query
select w.GroupID, g.Name, gm.Name, w.ID
from [dbo].[WeeklyEntry] as w
left outer join [dbo].[Group] as g on g.ID = w.GroupID
left outer join [dbo].[GroupMember] as gm on gm.GroupID = g.ID
AND gm.ID = w.DepositedByMemberID
order by w.GroupID
Strange Findings
If I include .ToList(); with each query like from we in _repository.GetAll().ToList() the entire query will work & give expected result without Any ERROR!!!
So if I convert each query return type to In-memory Or IEnumerable<> it is working as expected without any error but IQueryable<> query not working as expected.
New Code Snippet [Working]
var results = (from we in _repository.GetAll().ToList()
join g in groups on we.GroupID equals g.ID into grpjoin
from g in grpjoin.DefaultIfEmpty()
join gm in groupMembers on we.DepositedByMemberID equals gm.ID into gmjoin
from gm in gmjoin.DefaultIfEmpty()
where gm.GroupID == g.ID
select new {...}.ToList();
You can't join a database table with an in-memory collection (in your case, List):
Unable to create a constant value of type Only primitive types or enumeration types are supported in this context
You're converting these to in-memory collections by calling ToList:
var groups = _repositoryGroup.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.ID, s.Name }).ToList();
var groupMembers = _repositoryGroupMember.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.GroupID, s.ID, s.Name })
.ToList();
which you then try and join in the next bit of code.
If you simply remove those ToList calls, the join should work (you can keep the one for the final result, if you prefer it).
Remember that IEnumerable is lazy and will only actually run a SQL query if you "execute it" by iterating (usually via a foreach loop or some function like ToList).
Let me explain!
You have a GenericRepository<T> class, that is something like this:
public class GenericRepository<T>
{
MyDbContext dbContext;
public GenericRepository()
{
dbContext = new MyDbContext();
}
public IQueryable<T> GetAll()
{
// whatever
}
}
and then you have:
_repository = new GenericRepository<WeeklyEntry>();
_repositoryGroup = new GenericRepository<Group>();
_repositoryGroupMember = new GenericRepository<GroupMember>();
var groups = _repositoryGroup.GetAll().ToList(); // other query operators are irrelevant and removed
var groupMembers = _repositoryGroupMember.GetAll().ToList();
Calling ToList() runs your queries and bring the data to memory. Now you have two in-memory collection (List<T>).
When you write:
var results = from we in _repository.GetAll() // <-- this is IQueryable<T>
join g in groups // <-- this is List<T> (IEnumerable<T>)
on we.GroupID equals g.ID into grpjoin
...
you are joining an IQueryable, with an in-memory list. When running this query, EF has no way to know that your in-memory lists (groups and groupMembers) are actually queries from the database. It only sees two lists, containing some data. It has no way to translate that into SQL and hence throws an error.
To fix this, you should remove calls to ToList(). That way, you have three IQueryables that are joined together. EF can translate that into SQL, only if they are from a single DbContext. And since they are not, it throws another error, telling you exactly that.
You create an instance DbContext for each of the GenericRepository<T> instances. So, groups, groupMembers and we come from three different DbContexts.
To solve this error, you should somehow manage to use a single DbContext for all your GenericRepository<T>s.
For example:
using (var dbContext = new MyDbContext())
{
var groups = dbContext.Set<Group>();
var groupMembers = dbContext.Set<GroupMember>();
var results = from we in dbContext.Set<WeeklyEntry>()
join g in groups
on we.GroupID equals g.ID into grpjoin
...
}

string.Join in Linq to Entity queries

I'm currently shifting my data access code from L2S to Entity framework db first. I have problem with queries like following
var emps = (from emp in DataContext.setupEmployees
let contacts = DataContext.setupContacts.Where(x => x.EmployeeID == emp.EmployeeID).Select(x => x.Contact)
select new
{
EmployeeName = emp.EmployeeName,
Contacts = string.Join(", ", contacts.ToArray())
}).ToList();
EF tells me it can't convert the method string.join into stored expression. One obvious work-around is to bring the objects in memory (ToList, AsEnumerable etc) and then call string.Join method like
var emps = (from emp in DataContext.setupEmployees
let contacts = DataContext.setupContacts.Where(x => x.EmployeeID == emp.EmployeeID).Select(x => x.Contact)
select new
{
EmployeeName = emp.EmployeeName,
Contacts = contacts
}).ToList().Select(x=>new{
x.EmployeeName,
Contacts = string.Join(", ",x.Contacts)
});
This solution works perfectly but it's just that it's more verbose and I'd have to duplicate select clause in lots of queries that used to work fine in L2S.
My question: is there any way I can avoid writing this ToList thing and call string.Join and similar methods in EF query?
I would recommend creating Contacts as an IEnumerable<string> datatype instead of string datatype. You can than use Contacts to produce any output you like comma seperated etc...
You can change your query to look like this:
var emps = (from emp in DataContext.setupEmployees
select new
{
EmployeeName = emp.EmployeeName,
Contacts = DataContext.setupContacts.Where(x => x.EmployeeID == emp.EmployeeID).Select(x => x.Contact)
}).ToList();

How can I replicate "select someinteger from foo where someinteger like '%50%' in Linq to entities?

I have an ASP.NET MVC application that displays data in a table format. I want to give my users the ability to search the table, so I take a text string and pass it into my service layer to construct a query using Linq to Entities.
I want to search a number of columns using the string. Some of the columns are integers (order ids), but the user doesn't care about integers and strings. They want to type '1200' and get any order with '1200' in the order number, or '1200' in the address.
The problem is that I can't find a way to construct a Linq-to-Entities query that results in SQL that looks like this:
select orderid, address from orders where orderid like '%1200%' or address like '%1200%'
Database context:
public DbSet<Person> Persons { get; set; }
public DbSet<Worker> Workers { get; set; }
public DbSet<WorkerSignin> WorkerSignins { get; set; }
The Persons and Workers tables are in a 1 to 0..1 relationship. If a worker record exists, a person record must also exist. They share the same ID. A worker record doesn't have to exist, however.
The Workers and WorkerSignins tables are related, but it's not enforced because of a client requirement. The Worker has an id-card with a barcode number on it (dwccardnum), but there may be discrepancies between cards issued and records in the DB, so I record all cards scanned in WorkerSignins, regardless of whether there is a matching record in the Workers table.
Here is the code I am working with:
allWSI = signinRepo.GetAllQ()
.Where(jj => jj.dateforsignin == date)
.Select(a => a);
if (!string.IsNullOrEmpty(search))
{
allWSI = allWSI
.Join(workerRepo.GetAllQ(), s => s.dwccardnum, w => w.dwccardnum, (s, w) => new { s, w })
.DefaultIfEmpty()
.Join(personRepo.GetAllQ(), oj => oj.w.ID, p => p.ID, (oj, p) => new { oj, p }).DefaultIfEmpty()
.DefaultIfEmpty()
.Where(jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search) ||
jj.p.firstname1.Contains(search) ||
jj.p.firstname2.Contains(search) ||
jj.p.lastname1.Contains(search) ||
jj.p.lastname2.Contains(search))
.Select(a => a.oj.s);
}
The GetAllQ() methods return an IQueryable() object.
The problem is on this line:
.Where(jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search) ||
I get this error:
LINQ to Entities does not recognize the method 'System.String ToString(Int32)' method, and this method cannot be translated into a store expression."
If I take out the convert, and try this:
.Where(jj => jj.oj.w.dwccardnum.Contains(search) ||
I get this error:
'int' does not contain a definition for 'Contains' and the best extension method overload 'System.Linq.ParallelEnumerable.Contains(System.Linq.ParallelQuery, TSource)' has some invalid arguments
So the question is...
How do I construct a Where clause to generate a like '%string%' and execute it against a integer column using Linq to Entities? (e.g. without using LINQ to SQL)
One option is to replace ...
jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search)
... by:
jj => SqlFunctions.StringConvert((decimal)jj.oj.w.dwccardnum).Contains(search)
SqlFunctions is a static class in namespace System.Data.Objects.SqlClient and I believe it only works with SQL Server. The weird cast to decimal is necessary because StringConvert doesn't have an overload for an int and without the cast the compiler complains that it cannot select the right overload unambiguously. (It has one for decimal? and one for double?.) But I just tested that the code above works indeed (with SQL Server and assuming dwccardnum is an int).
Try this
if (!string.IsNullOrEmpty(search))
{
int cardnum;
bool searchIsInt = int.TryParse(search, out cardnum);
allWSI = allWSI
.Join(workerRepo.GetAllQ(), s => s.dwccardnum, w => w.dwccardnum, (s, w) => new { s, w })
.DefaultIfEmpty()
.Join(personRepo.GetAllQ(), oj => oj.w.ID, p => p.ID, (oj, p) => new { oj, p }).DefaultIfEmpty()
.DefaultIfEmpty()
.Where(jj => (searchIsInt ? jj.oj.w.dwccardnum == cardnum : true) ||
jj.p.firstname1.Contains(search) ||
jj.p.firstname2.Contains(search) ||
jj.p.lastname1.Contains(search) ||
jj.p.lastname2.Contains(search))
.Select(a => a.oj.s);
}
Basically, you're first checking to see if the search is an int and then use it in your linq if it is.