SelectMany converts query to Enumerable List. How to avoid it? - c#

I have a MVC controller action that does a database query this way:
var marcaciones = db.Marcacion
where db is the database context in Entity Framework, and Marcacion is a database table. After that instruction, marcaciones type becomes
System.Data.Entity.DbSet`1[CasinosCloud.Models.Marcacion]
That allows to add any filter before framework actually executes the query in database.
So far, so good.
However, depending on certain condition, marcaciones variable is assigned in a different way.
The database model is such that marcaciones entity in database is a child of another entity. To get that marcaciones list, I can do this:
var marcaciones = trabajador.ServicioSupervisado.SelectMany(s => s.Marcacion).AsQueryable();
As you can infer from instruction above, trabajador is a parent database entity that have many ServicioSupervisado entities, which, in turn, can have many Marcacion entities.
Since marcaciones variable is the same as the marcaciones variable I showed before, I have to convert to Queryable.
After executing the above instruction, marcaciones type becomes;
{System.Linq.Enumerable+<SelectManyIterator>d__17`2
[CasinosCloud.Models.Servicio,CasinosCloud.Models.Marcacion]}
That mean query is actually converted to an Enumerable List.
All that works when no other filter is applied. When I add query filter I got problems with the second form. First, the whole web page is slower because all filters are applied in a memory list, not in the database, and second, I have problems with string comparisons, specially when I try to find a text in lowercase when in database is stored in uppercase. Of course, nothing is found in such a case.
I think the problem is reduced by solving the type issue. Why after calling SelectMany, the query is actually executed and converted to an Enumerable List? Is there a way to avoid this and all that to be executed in database? Maybe I should rewrite that instruction not using SelectMany. I tried by using db.Marcacion.Insersect() to do the intersection with this code, but the same problem occurs:
trabajador.ServicioSupervisado.SelectMany(s => s.Marcacion)
EDIT:
Query I want to execute in database takes the following form:
For the first way:
SELECT m.*
FROM Marcacion m
For the second way:
SELECT m.*
FROM Marcacion m
INNER JOIN Servicio s ON s.ServicioId = m.ServicioId
INNER JOIN Trabajador t ON t.TrabajadorId = s.TrabajadorId
WHERE t.TrabajadorId = 1069
EDIT 2:
For the second way, I tried with:
marcaciones = marcaciones.Where(m => trabajador.ServicioSupervisado.Any(s => s.ServicioId == m.ServicioId));
After that, when query is actually executed in database, this error happens:
System.NotSupportedException: 'Unable to create a constant value of type 'CasinosCloud.Models.Servicio'. Only primitive types or enumeration types are supported in this context.'

I solved it by writing the query this way:
var servicios = trabajador.ServicioSupervisado.Select(s => s.ServicioId);
marcaciones = marcaciones.Where(m => servicios.Any(s => s == m.ServicioId));
That way query is executed when I call ToList(), and works in both cases I told about, being very fast in both since query is run directly in database with all filters applied.
By seeing the database log, the final query executed by Entity Framework was:
SELECT
[Extent1].[MarcacionId] AS [MarcacionId],
[Extent1].[MonitorId] AS [MonitorId],
[Extent1].[TrabajadorId] AS [TrabajadorId],
[Extent1].[EmpresaId] AS [EmpresaId],
[Extent1].[ServicioId] AS [ServicioId],
[Extent1].[MarcacionFechaHora] AS [MarcacionFechaHora],
[Extent1].[MarcacionEntradaSalida] AS [MarcacionEntradaSalida],
[Extent1].[MarcacionChecksum] AS [MarcacionChecksum],
[Extent1].[MarcacionEquipo] AS [MarcacionEquipo],
[Extent1].[MarcacionEsManual] AS [MarcacionEsManual],
[Extent1].[MarcacionCreadoEn] AS [MarcacionCreadoEn],
[Extent1].[MarcacionActualizadoEn] AS [MarcacionActualizadoEn],
[Extent1].[MarcacionIndice] AS [MarcacionIndice]
FROM [dbo].[Marcacion] AS [Extent1]
INNER JOIN [dbo].[Trabajador] AS [Extent2] ON [Extent1].[TrabajadorId] = [Extent2].[TrabajadorId]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
WHERE 3 = [Extent1].[ServicioId]
)) AND ([Extent2].[TrabajadorNombres] + N' ' + [Extent2].[TrabajadorApellidos] LIKE #p__linq__0 ESCAPE N'~') AND ([Extent1].[MarcacionFechaHora] >= #p__linq__1) AND ([Extent1].[MarcacionFechaHora] <= #p__linq__2)

Related

Using two different data context in a LINQ JOIN Query

I went through the many questions that were asked regarding this and tried to find solution but no luck. So here is my situation:
private IQueryable<tblB> MT;
var IDs = (from z in db1.tblA
where z.TA == User.Identity.Name
select z).ToArray();
MT = from s in db2.tblB
join a in IDs on s.BP equals a.BP
select new tblB() { LastName = s.LastName});
return View(MT.ToPagedList(pageNumber, pageSize));
I'm getting exception at the return statement - $exception {"Unable to create a constant value of type 'tblA'. Only primitive types or enumeration types are supported in this context."} System.NotSupportedException
When I debug IDs array, I see it has data from tblA but 2nd query with join doesn't seem to work. What mistake am I making. Help!!!
You need to use Contains in order to generate and IN sql clause:
First, change the first query to return the primitive data you need:
var IDs = (from z in db1.tblA
where z.TA == User.Identity.Name
select z.BP).ToArray();
Then use that in-memory list in the second query:
MT = from s in db2.tblB
where IDs.Contains(s.BP)
select new tblB() { LastName = s.LastName});
By the way, this is not a 2 contexts operations. You're loading data from the first context into memory (notice the .ToArray()) and then using these in-memory data to query the second context.
So you want as a result all elements of tblB that have a property BP equal to at least one of the BP properties of the elements in IDs.
The problem is that after ToList() IDs is in local memory, while your db1.tblB is on the database. You have to bring both sequences to the same platform, either both to your database, or both to local memory. Whatever is more efficient depends on the actual sizes of the sequences and of the results
Use Contains if you want to perform the query on database side. The complete list of IDs will be transferred to the database, where the query will be executed and the results will be transferred to local memory.
Use this method if IDs is fairly short and your result is not almost the complete tblA
var result = db2.tblB
.Where(elementOfTableB => IDs.Contains(elementOftableB);
No need to create a new tblB object, apparently you want the complete tblB object.
Use AsEnumerable if you expect that there are a lot of IDs in comparison to the number of element in tblB. Transferring the IDs to the database would take considerably more time than transferring the complete tblB to local memory.
db2.TblB // take all tblBelements
.AsEnumerable() // bring to local memory
.join(IDs, // join with list IDs
tblBElement => tblBElement.BP // take from each tblBElement the BP
idElement => idElement.BP // take from each ID the BP
(tblBElement, idElement) => // when they match take the two records
// and get what you want.
tblBElement); // in this case the complete tblBElement

SQL generated from LINQ not consistent

I am using Telerik Open/Data Access ORM against an ORACLE.
Why do these two statements result in different SQL commands?
Statement #1
IQueryable<WITransmits> query = from wiTransmits in uow.DbContext.StatusMessages
select wiTransmits;
query = query.Where(e=>e.MessageID == id);
Results in the following SQL
SELECT
a."MESSAGE_ID" COL1,
-- additional fields
FROM "XFE_REP"."WI_TRANSMITS" a
WHERE
a."MESSAGE_ID" = :p0
Statement #2
IQueryable<WITransmits> query = from wiTransmits in uow.DbContext.StatusMessages
select new WITransmits
{
MessageID = wiTranmits.MessageID,
Name = wiTransmits.Name
};
query = query.Where(e=>e.MessageID == id);
Results in the following SQL
SELECT
a."MESSAGE_ID" COL1,
-- additional fields
FROM "XFE_REP"."WI_TRANSMITS" a
The query generated with the second statement #2 returns, obviously EVERY record in the table when I only want the one. Millions of records make this prohibitive.
Telerik Data Access will try to split each query into database-side and client-side (or in-memory LINQ if you prefer it).
Having projection with select new is sure trigger that will make everything in your LINQ expression tree after the projection to go to the client side.
Meaning in your second case you have inefficient LINQ query as any filtering is applied in-memory and you have already transported a lot of unnecessary data.
If you want compose LINQ expressions in the way done in case 2, you can append the Select clause last or explicitly convert the result to IEnumerable<T> to make it obvious that any further processing will be done in-memory.
The first query returns the full object defined, so any additional limitations (like Where) can be appended to it before it is actually being run. Therefore the query can be combined as you showed.
The second one returns a new object, which can be whatever type and contain whatever information. Therefore the query is sent to the database as "return everything" and after the objects have been created all but the ones that match the Where clause are discarded.
Even though the type were the same in both of them, think of this situation:
var query = from wiTransmits in uow.DbContext.StatusMessages
select new WITransmits
{
MessageID = wiTranmits.MessageID * 4 - 2,
Name = wiTransmits.Name
};
How would you combine the Where query now? Sure, you could go through the code inside the new object creation and try to move it outside, but since there can be anything it is not feasible. What if the checkup is some lookup function? What if it's not deterministic?
Therefore if you create new objects based on the database objects there will be a border where the objects will be retrieved and then further queries will be done in memory.

LINQ left-join Entity Frame work - Unable to create a constant value of type

There are simular questions with answers that do not work in my situation.
I'm getting a
Unable to create a constant value of type '.Model.featureoptions'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Using Entity First, EntityFramework 4.1, MVC3, C# 4.
vehicles is a table of vehicle details, owners is a table of vehicle owners. vehicles and owners are inner joined and that works.
features table is a list of optional features e.g. sunroof, paint, etc. featureOptions is a list of the available options for a feature. e.g. paint could be 'pearlescent', 'matalic' and sunroof could be 'glass pop-up', 'title + slide'.
vehicleFeatures is a list of chosen options for a vehicle, for a particulare feature a vehicle can have zero or one record.
In this query feature1 should be null or the chosen value for a feature (i.e. the chosen sunroof option) and feature2 should be null or the chosen value for a different feature (i.e. the chosen paint option)
var query = (from v in _entities.vehicles
join o
in _entities.owners
on v.OwnerID equals o.OwnerID
// Some more inner joins
select new
{
// <code snipped >
// o. fields and v. fields
// </ code snipped>
feature1 = (from feature1
in _entities.vehiclefeatures
.Where ( f_1 => f_1.VehicleID == v.VehicleID)
join feature1_fo
in _entities.featureoptions
on feature1.FeatureOptionID equals feature1_fo.FeatureOptionID
join feature1_f
in _entities.features
.Where (bt_f => bt_f.CodeEnum==1)
on feature1_fo.FeatureID equals feature1_f.FeatureID
select new featureoptionsDTO () { Option = feature1_fo.Option }
),
feature2 = (from feature2
in _entities.vehiclefeatures
.Where(f_2 => f_2.VehicleID == v.VehicleID)
join feature2_fo
in _entities.featureoptions
on feature2.FeatureOptionID equals feature2_fo.FeatureOptionID
join feature2_f
in _entities.features
.Where(feature2_f => feature2_f.CodeEnum == 2)
on feature2_fo.FeatureID equals feature2_f.FeatureID
select new featureoptionsDTO() { Option = feature2_fo.Option }
)
}
);
foreach (var vehicle in query) // Exception here
{
}
the
feature1 = (from ..
and
feature2 = (from ..
are causing the
Unable to create a constant value of type '.Model.featureoptions'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
I understand that LINQ is trying to create an entity, how can I get it to create an anonymous (or own class) instead?
Unfortunately Entity Framework can't handle select clauses which construct arbitrary types in LINQ to Entities queries. I've tripped over this one a few times myself, and it's quite annoying. It is, however, quite necessary as LINQ to Entities queries are translated into SQL to run on the database, and the database can't handle the construction of .NET objects. It might be nice to be able to do it at the end of the query, but it can certainly never be allowed in the middle.
What I tend to do is write a query which produces exactly the input required to the constructors all in LINQ to Entities, so that it runs on the database. Then call ToEnumerable() on the IQueryable you get from that, which turns it into an IEnumerable, and after that you're in LINQ to Objects so you can do whatever you like in your Select().
I resolved this by using a view in the database to do the outer joins and the linq queries the entity assciated with the view.
By doing the left outer in the database means the outer joins are done earlier, possibly making it a bit quicker. The linq is tidier and just has to do what it needs to, which in this case is filtering.

Sort Linq list with one column

I guess it should be really simple, but i cannot find how to do it.
I have a linq query, that selects one column, of type int, and i need it sorted.
var values = (from p in context.Products
where p.LockedSince == null
select Convert.ToInt32(p.SearchColumn3)).Distinct();
values = values.OrderBy(x => x);
SearchColumn3 is op type string, but i only contains integers. So i thought, converting to Int32 and ordering would definitely give me a nice 1,2,3 sorted list of values. But instead, the list stays ordered like it were strings.
199 20 201
Update:
I've done some tests with C# code and LinqPad.
LinqPad generates the following SQL:
SELECT [t2].[value]
FROM (
SELECT DISTINCT [t1].[value]
FROM (
SELECT CONVERT(Int,[t0].[SearchColumn3]) AS [value], [t0].[LockedSince], [t0].[SearchColumn3]
FROM [Product] AS [t0]
) AS [t1]
WHERE ([t1].[LockedSince] IS NULL)
) AS [t2]
ORDER BY [t2].[value]
And my SQL profiler says that my C# code generates this piece of SQL:
SELECT DISTINCT a.[SearchColumn3] AS COL1
FROM [Product] a
WHERE a.[LockedSince] IS NULL
ORDER BY a.[SearchColumn3]
So it look like C# Linq code just omits the Convert.ToInt32.
Can anyone say something useful about this?
[Disclaimer - I work at Telerik]
You can solve this problem with Telerik OpenAccess ORM too. Here is what i would suggest in this case.
var values = (from p in context.Products
where p.LockedSince == null
orderby "cast({0} as integer)".SQL<int>(p.SearchColumn3)
select "cast({0} as integer)".SQL<int>(p.SearchColumn3)).ToList().Distinct();
OpenAccess provides the SQL extension method, which gives you the ability to add some specific sql code to the generated sql statement.
We have started working on improving this behavior.
Thank you for pointing this out.
Regards
Ralph
Same answer as one my other questions, it turns out that the Linq provider i'm using, the one that comes with Telerik OpenAccess ORM does things different than the standard Linq to SQL provider! See the SQL i've posted in my opening post! I totally wasn't expecting something like this, but i seem that the Telerik OpenAccess thing still needs a lot of improvement. So be careful before you start using it. It looks nice, but it has some serious shortcomings.
I can't replicate this problem. But just make sure you're enumerating the collection when you inspect it. How are you checking the result?
values = values.OrderBy(x => x);
foreach (var v in values)
{
Console.WriteLine(v.ToString());
}
Remember, this won't change the order of the records in the database or anywhere else - only the order that you can retrieve them from the values enumeration.
Because your values variable is a result of a Linq expression, so that it doest not really have values until you calling a method such as ToList, ToArray, etc.
Get back to your example, the variable x in OrderBy method, will be treated as p.SearchColumn3 and therefore, it's a string.
To avoid that, you need to let p.SearchColumn3 become integer before OrderBy method.
You should add a let statement in to your code as below:
var values = (from p in context.Products
where p.LockedSince == null
let val = Convert.ToInt32(p.SearchColumn3)
select val).Distinct();
values = values.OrderBy(x => x);
In addition, you can combine order by statement with the first, it will be fine.

Entity Framework SQL Query Execution

Using the Entity Framework, when one executes a query on lets say 2000 records requiring a groupby and some other calculations, does the query get executed on the server and only the results sent over to the client or is it all sent over to the client and then executed?
This using SQL Server.
I'm looking into this, as I'm going to be starting a project where there will be loads of queries required on a huge database and want to know if this will produce a significant load on the network, if using the Entity Framework.
I would think all database querying is done on the server side (where the database is!) and the results are passed over. However, in Linq you have what's known as Delayed Execution (lazily loaded) so your information isn't actually retrieved until you try to access it e.g. calling ToList() or accessing a property (related table).
You have the option to use the LoadWith to do eager loading if you require it.
So in terms of performance if you only really want to make 1 trip to the Database for your query (which has related tables) I would advise using the LoadWith options. However, it does really depend on the particular situation.
It's always executed on SQL Server. This also means sometimes you have to change this:
from q in ctx.Bar
where q.Id == new Guid(someString)
select q
to
Guid g = new Guid(someString);
from q in ctx.Bar
where q.Id == g
select q
This is because the constructor call cannot be translated to SQL.
Sql's groupby and linq's groupby return differently shaped results.
Sql's groupby returns keys and aggregates (no group members)
Linq's groupby returns keys and group members.
If you use those group members, they must be (re-)fetched by the grouping key. This can result in +1 database roundtrip per group.
well, i had the same question some time ago.
basically: your linq-statement is converted to a sql-statement. however: some groups will get translated, others not - depending on how you write your statement.
so yes - both is possible
example:
var a = (from entity in myTable where entity.Property == 1 select entity).ToList();
versus
var a = (from entity in myTable.ToList() where entity.Property == 1 select entity).ToList();

Categories