EntityFramework do Paging on a query with a join - entity-framework

I have a query with a left join in it:
var query = (from v in context.Vehicles
//left join vehicleAttributes
join va in context.VehicleAttributes on v.VehicleId equals va.VehicleId into vAttributes
from vehicleAttributes in vAttributes.DefaultIfEmpty()
where v.FleetId == fleetId
select new { v, vehicleAttributes });
And now I need to do a paging on it.
this works but gets all rows, so much more than i actually need
query.ToList().Select(x => x.v).Distinct().Skip(10 * (page - 1)).Take(10).ToList();
this is what I tried instead but now I don't have the joint values
query.Select(x => x.v).Distinct().ToList().Skip(10 * (page - 1)).Take(10).ToList();
any ideas?
Thanks

The ToList() triggers the call to the database so you need to only do this after you apply the Skip and Take. You'll need an OrderBy clause as well.
You should be able to do something like this:
var data = (from v in context.Vehicles
join va in context.VehicleAttributes on v.VehicleId equals va.VehicleId into vAttributes
from vehicleAttributes in vAttributes.DefaultIfEmpty()
where v.FleetId == fleetId
select new { v, vehicleAttributes })
.OrderBy(p => p.v.FleetId)
.Skip(10 * (page - 1))
.Take(10)
.ToList();

Don't call ToList before Skip. Doing so will return all the records in the database that match your query.
To use Skip on an EntityFramework query, you need to have an instance of IOrderedQueryable, so you need an OrderBy clause.
query
.Select(x => x.v)
.Distinct()
.OrderBy(v => v.FleetId)
.Skip(10 * (page - 1))
.Take(10).ToList();
I've got a project that takes care of a lot of this functionality. It's available on NuGet (with an MVC counterpart) and Google Code.
Using it would look something like this:
var factory = new Pagination.PageSourceFactory {
MaxItemsPerPage = 50,
DefaultItemsPerPage = 20
};
var source = factory.CreateSource(query, page);

Related

Take each first element of the group by

How can I take each first element in the group with EF 5 ?
var result = await context.SomeDbSet
.Where(...)
.GroupBy(x => new { x.SomeField, ... })
.Select(x => x.First())
.ToListAsync();
I am getting not supported exception.
How to correctly rewrite query? Thanks.
You cannot do that with grouping. SQL has a limitation - with GROUP BY you can select only grouping keys and aggregation result. This limitation for sure extended to LINQ to Entities - after GroupBy you can select only grouping keys and aggregation result.
Such result can be achieved by SQL and Window functions:
SELECT
r.*,
FROM
(
SELECT
s.*,
ROW_NUMBER() OVER(PARTITION BY s.SomeField1, s.SomeField2 ORDER BY s.SomeDate) AS RN
FROM SomeDbSet s
WHERE ...
) r
WHERE r.RN = 1
For those who want to stay with LINQ, I propose extension (disclaimer: I'm extension creator) linq2db.EntityFrameworkCore
And you can write query above via LINQ
var rnQuery =
from s in context.SomeDbSet
where ...
select new
{
Data = s,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(s.SomeField1, s.SomeField2)
.OrderBy(s.SomeDate)
.ToValue()
}
var resultQuery = await rnQuery
.Where(r => r.RN == 1)
.Select(r => r.Data)
.ToLinqToDB();
var result = resultQuery.ToList();
// async variant may need ToListAsyncLinqToDB() call
// because of collision in async extension methods between EF Core and linq2db
var result = await resultQuery.ToListAsyncLinqToDB();

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

how to implement EF inner join with given filter?

SELECT DISTINCT k.* FROM [dbo].[kinds] K
INNER JOIN KindGraphic KG ON K.KindId = KG.KindId
INNER JOIN Graphics G ON KG.GraphicId = G.GraphicId
WHERE K.CategoryType = 2
AND G.IsSpecial = 1
How to write this in EF ? I am new to EF. I m using dbContex for my MVC project.
Make Note that "KindGraphic" table is mapped liked this ways
so I can not use this method https://stackoverflow.com/a/21986882/3264939
modelBuilder.Entity<Kind>()
.HasMany(c => c.Graphics)
.WithMany(g => g.Kinds)
.Map(t => t.MapLeftKey("KindId")
.MapRightKey("GraphicId")
.ToTable("KindGraphic"));
The result from your original query is some kind of complex result. So without selecting the exact columns (instead of using *), I assume the result is contained in an anonymous type like this:
{
Kind,
Graphic
}
I understand that KindGraphic is some kind of junction (join) table, so it's info is not important to include in the result (we can access KindId from Kind and GraphicId from Graphic). Here is the LINQ query:
var result = context.kinds.Where(e => e.CategoryType == 2)
.SelectMany(e=> e.Graphics.Where(g=>g.IsSpecial == 1),
(e, g) => new { Kind = e, Graphic = g} );
After your edit to use distinct, the query can be translated as you want all kinds having category type = 2 and any Graphics with IsSpecial = 1. So it should be like this:
var result = context.kinds.Where(e => e.CategoryType == 2 &&
e.Graphics.Any(g=>g.IsSpecial == 1));

EntityFramework - how to get rows that match any of the IDS from another table where the userId matches?

I'm not sure how to get the rows I need in EF. In SQL it would look something like this:
SELECT * FROM [Recipes]
JOIN [UserFavorites] ON [UserFavorites].[RecipeId] = [Recipes].[Id]
WHERE [UserFavorites].[UserId] = #UserId
I know how to get the userfavorites that match the user id like this:
db.UserFavorites.Where(x => x.UserId == userId
But then how do I get all of the recipes that match the recipeIds inside of those userfavorites?
You can either use LINQ and construct a query:
var recipes = from r in db.Recipes
join f in db.UserFavorites on r.Id equals f.RecipeId
where f.UserId = userId
select r
or you can use the lambda syntax with navigation properties, assuming you have them set up for the relationships in question
var recipes = db.Recipes.Where(r => r.UserFavorites.Any(f => f.UserId == userId));
You can, of course, construct the actual query that's described in the first section using the equivalent lambda syntax (since the query syntax is just a language feature that compiles down to the equivalent calls to the extension methods using anonymous delegates), but that tends to be a little more difficult to read.
How about:
var recipes = context.Recipes
.SelectMany(r => r.Users, (r, u) => new { Recipe = r, User = u }
.Where(o => o.User.UserId = userId)
.Select(o => o.Recipe);
This implies UserFavorites is the intersection table between Recipes and Users
Recipes >- UserFavorites -< Users

Converting T-SQL to Linq

I am using Entitry Framework 4.1 and I am struggling to understand how the convert the below query which uses joins and aggregate methods to a Linq to Entities call in the DomainService.
SELECT tblTime.Period As Timeline, COUNT(tblEngineeringDashboard_ItemList.ID) AS Items
FROM tblEngineeringDashboard_ItemList INNER JOIN
tblTime ON tblEngineeringDashboard_ItemList.TimeID = tblTime.ID
GROUP BY tblTime.Period
ORDER BY tblTime.Period
Can anyone provide help.
Possible Solution
Dim var = From i In ObjectContext.tblEngineeringDashboard_ItemList
Join t In ObjectContext.tblTimes On i.TimeID Equals t.ID
Group By i.TimeID Into Group
Select DateStart = (From n In ObjectContext.tblTimes Where n.ID = TimeID Select n.Period), PartCount = Group.Count
Phil
The first thing which comes to mind is:
var q = from t in Context.Time
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
However, the original SQL had an INNER JOIN, which would reject tblTime records without any matching records in tblEngineeringDashboard_ItemList. So you may want:
var q = from t in Context.Time
where t.ItemList.Any()
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
You can also flip the query around:
var q = from i in Context.EngineeringDashboardItemList
where i.Time != null
group i by i.Time.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = g.Count()
};
Does this work?
tblEngineeringDashboard_ItemList
.Join(tblTime,ed => ed.TimeID ,t => t.ID, (ed,t) => new{ed,t})
.GoupBy(g => g.t.Period)
.Select(s => new
{
Timeline = s.Key,
Items = s.Count()
}
)
.OrderBy(o => o.Timeline)
While converting from sql to linq isn't an ideal approach (you should think directly in linq, translating your need to a linq query), the query you posted is rather simple.
var grouped = tblTime.OrderBy(c => c.Period).GroupBy(c => c.Period).Select(c =>
new {
timeline = c.Key,
count = c.SelectMany(x => x.tblEngineeringDashboard).Count()
});
*Edit: There, fixed. Everything on the L2E engine.
This provided that there are correct foreign keys between the tables (thus you don't have to declare the join manually).