Why are anonymous Types in EF4 different from LINQ to SQL ones? - entity-framework

I have the following query in LINQ to SQL to fetch all records from a Table that are not already in a jointable.
// <param name="id">The ID of the Person</param>
IEnumberable<object> GetUnassignedClients(int id)
{
_db.Clients
.Select(i => new
{
Client_id = i.Id,
Person_id = id,
Cid = id + "." + i.Id // Please don't ask why I do this. I just have to do it
// ... some more fields
})
.Where(o =>
!_db.Clients_Persons
.Where(t => t.Person_id == id)
.Select(t => t.Client_id)
.Contains(o.Client_id))
.Distinct().ToList();
}
Now I have started a migration to EF4 but the "Cid" part of the anonymous type with the combination ToList() (ToList() triggered the exception is a simplified testcase without the WHERE condition) fails with the exception:
Unable to create a constant value of
type 'System.Object'. Only primitive
types ('such as Int32, String, and
Guid') are supported in this context.
Why is that so or am I missing something here?

EF does not know how to translate the expression id + "." + i.Id into valid SQL which is why it fails. You have to tell EF that it needs to convert id from an integer to a string. You can do this using the SqlFunctions class in the following way:
var ret = _db.Clients
.Select(i => new
{
Client_id = i.Id,
Person_id = id,
Cid = SqlFunctions.StringConvert((double) id) + "." + SqlFunctions.StringConvert((double) i.Id) // Please don't ask why I do this. I just have to do it
// ... some more fields
})
.Where(o =>
!_db.Clients_Persons
.Where(t => t.Person_id == id)
.Select(t => t.Client_id)
.Contains(id)
)
.Distinct()
.ToList()
;

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 Update The LINQ expression 'x' could not be translated

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

Can we use a DbContext within a Linq Select Expression?

Is it a good practice or a convention to use dbContext within a Select linq query as following . If not what is the right convention or alternative to do so ?
dbContext.Employees.Select(x=>{
**Name = dbContext.ContactInformation.Where(y=>y.Id = x.Id),**
Id = x.Id
})
Why do not you have a navigationPropery from Employee to ContactInformation? look here
var result = dbContext.Employees.Include(e => e.ContactInformation);
You can also use a Join.
var res = dbContext.Employees.Join(ContactInformation,
e => e.Id,
c => c.Id,
(e, c) => new { e, c })
.Select(ec => ec.e);

Entity Framework LINQ Query match all members of child collection

I have a Site table that has a many-to-many relationship with a UtilityServiceConnection table using a linking table called LinkSiteUtilityServiceConnection. Given a set of ServiceConnectionIds, I need to locate the Site that is exclusively linked to all of them and no more. I think I should be able to write the query using All on the collection but it's not working as expected.
var serviceConnectionIds = new[] { 546892, 546911, 546923 };
var sites1 = db.Sites
.Where(x => x.LinkSiteUtilityServiceConnections.All(y => serviceConnectionIds.Contains(y.UtilityServiceConnectionId)))
.ToList();
Assert.AreEqual(1, sites1.Count); //fails
This produces the query below that returns ~250,000 records when I expect to get one.
SELECT [Extent1].*
FROM [dbo].[Site] AS [Extent1]
WHERE NOT EXISTS (SELECT 1 AS [C1]
FROM [dbo].[LinkSiteUtilityServiceConnection] AS [Extent2]
WHERE ([Extent1].[SiteId] = [Extent2].[SiteId])
AND ((NOT ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923)))
OR (CASE
WHEN ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923)) THEN cast(1 as bit)
WHEN (NOT ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923))) THEN cast(0 as bit)
END IS NULL)))
Why isn't All working as I expect? What's the best way to write this query?
check this code:
query 1:
var sites1 = db.Sites
.Where(x => serviceConnectionIds.All(y =>
x.LinkSiteUtilityServiceConnections
.Select(u => u.UtilityServiceConnectionId).Contains(y)))
.ToList();
query 2:
var query = db.Posts.AsQueryable();
var sites1 = serviceConnectionIds.Aggregate(query,
(current, item) => current.Where(e => e.LinkSiteUtilityServiceConnections
.Any(c => c.UtilityServiceConnectionId== item))).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.