How to Select subset of columns from SingleOrDefault LINQ query in EF - c#

How can I select subset of all columns while using SingleOrDefault query? For example, following LINQ expression
var personid = ctx.persons.SingleOrDefault(p => p.login == currentLogin)?.personid;
will compile into SELECT TOP 1 * FROM ... type of query. I would like to Select() only the columns I am interested in, e.g. statement producing SELECT TOP 1 personid, myColumn FROM ... under-hood.
Please note, that the question cannot possibly be duplicate of linked question. I am interested in context of Single/SingleOrDefault not generic solution for LINQ. Chaining .SingleOrDefault() with .Select() is not possible for apparent reasons: Single<T>returns single object of type T (or throws) which clearly does not implement IEnumerable<T> and cannot be Select()ed upon.

var personid = ctx.persons
.Where(p => p.login == currentLogin)
.Select(p => new {Prop = p.Column, personid = p.id})
.SingleOrDefault()?.personid;
Would probably work.

Related

Conversion of SQL group by to Entity Framework Lambda expression

I have 3 tables A which has an ID and other fields, B which has an ID, and C has a many to many relation for the ID's in A and B
I made a query that gets the results that I need
select result.*
from
(SELECT max(A.AID) as AID
FROM A, C
where A.AID = C.AID
group by C.BID) as x, A as result
where result.AID = x.AID
and I want to convert it to a lambda expression in entity framework. But currently the query is not efficient enough. How would I make the lamda expression in EF and also make it more efficient?
If you first order your results descending by the ID you want the max of, and then GroupBy that ID in the related table, then Select the First from each grouping you should get the results you need.
var groupedResults = tempContext.A.OrderByDescending(a => a.AID
).GroupBy(group => group.C.AID).Select(group => group.FirstOrDefault());
This should return you an IQueryable of the A entity that you can then continue to modify with additional clauses if needed.

Removing duplicate rows in database with primary key using Distinct()

I have some duplicate values in my database so I am using Linq to Entity to remove them with the code below. The problem is that there is an autonumber primary key in RosterSummaryData_Subject_Local, which invalidates the line var distinctRows = allRows.Distinct();
So, even if all the rows are the same, distinct won't work because the pk is different. Is there anyway to discredit the pk in the distinct? Or anyway to remove it from the query so it becomes a non issue. Just to note I want the query to return an IQueryable of my entity type so I can use the RemoveRange() method on the enttiy to remove the duplicates.
var allRows = (from subjLocal in customerContext.RosterSummaryData_Subject_Local
select subjLocal);
var distinctRows = allRows.Distinct();
if (allRows.Count() == distinctRows.Count())
{
return;
}
else
{
var rowsToDelete = allRows.Where(a => a != distinctRows);
customerContext.RosterSummaryData_Subject_Local.RemoveRange(rowsToDelete);
}
EDIT
I realized that to properly bring back distinct rows, all I have to do is select all the items except primary key:
var distinctRows = allRows
.Select(a => new {a.fkRosterSetID, a.fkTestInstanceID, a.fkTestTypeID,
a.fkSchoolYearID, a.fkRosterTypeID, a.fkDistrictID,
a.fkSchoolID, a.fkGradeID, a.fkDepartmentID,
a.fkCourseID, a.fkPeriodID, a.fkDemoCommonCodeID,
a.fkDemoCommonCategoryID, a.fkTest_SubjectID})
.Distinct();
The problem is that I cannot fetch the duplicate rows with the code below because the ! operator does not work with anonymous types(the variable distinctRows is an anonymous type because I didn't select all the columns):
var rowsToDelete = allRows.Where(a => a != distinctRows);
Any help?
you can try this:
var allRows = (from subjLocal in customerContext.RosterSummaryData_Subject_Local
select subjLocal).ToList();
var distinctRows = allRows.Distinct().ToList();
Since you will be dealing with list objects, then in your original else statement you can do this:
else
{
var rowsToDelete = allRows.Where(a => !distinctRows.Contains(a));
customerContext.RosterSummaryData_Subject_Local.RemoveRange(rowsToDelete);
}
To handle your issue with Distinct() and the autonumberID in the database, there are two solutions I can think of.
One is you can bring in the MoreLinq library, it's a Nuget package. then you can use the MoreLinq method DistinctBy():
allRows.DistinctBy(a => a.SomePropertyToUse);
Or the other route would be to use an IEqualityComparer with the regular .Distinct() Linq Method. You can check out this SO question for more info on using an IEqualityComparer in the .Distinct() method. using distinct with IEqualityComparer
maybe you need to check for each one of the fields in your customerContext.RosterSummaryData_Subject_Local to see which one is different

LINQ to Entities does not recognize the method Generic.List(int) to Generic.IEnumerable(int) method

The database contains Orders.
Orders can be contained within a group of Orders.
For every group of Orders it could contain 1 to many Orders.
However, Orders could have a NULL value assigned GroupOrderId as previous Orders did not have the grouping concept. Only new Orders enforce the concept of being added to a group.
The class structure to be populated in order to perform actions on each Order is
public class OrdersGroup
{
public int? GroupOrderId { get; set; }
public List<int> OrderIds { get; set; }
}
The linq statement
var workPacketOrdersList = (from o in db.Orders
where
o.GroupOrderId >= groupOrderIdMin && o.GroupOrderId <= groupOrderIdMax &&
o.IsDeleted == false
orderby o.WorkPacketId ascending
group o by o.WorkPacketId
into grp
select new OrdersGroup
{
GroupOrderId = grp.Key,
OrderIds = grp.Select(g => g.OrderId).ToList()
}).ToList();
Full exception
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[System.Int32] ToList[Int32](System.Collections.Generic.IEnumerable`1[System.Int32])' method, and this method cannot be translated into a store expression.
I see that the returned type of the linq query is a List<OrdersGroup>.
If the final .ToList() is omitted from the query than the return type becomes an IQueryable<OrdersGroup>
No matter what action is performed next the result is an exception that this method cannot be translated into a store expression.
I have tried to remove the specific select new OrdersGroup into a more generic select new and then perform actions on this result only to find the same store expression exception.
Can someone give some insight into where this linq is incorrect?
this is the part that's failing - grp.Select(g => g.OrderId).ToList() - you can't have a .ToList() in the select clause. remove that and you should be fine.
The problem is that LINQ to Entities is attempting to convert your query into SQL. It doesn't know how translate ToList into SQL, so that's the problem. You need to remove the call to ToList from inside your query.
That is,
OrderIds = grp.Select(g => g.OrderId).ToList()
LINQ to Entities can not convert that to SQL. Remove the call
OrderIds = grp.Select(g => g.OrderId)
and if you need OrderIds to be a List<int>, do the call to ToList after you execute the query.
It's because you're trying to call ToList() in a part of the query that will become raw SQL and executed at the source (ie SQL Server, not the CLR). I don't know exactly what your data is so I can't necessarily make an accurate recommendation on how to fix it but I would try taking making the ToList() call after this query or just not making it all. It's likely IEnumberable will offer whatever functionality you need which is what the Select will return if you remove the ToList() call.
By the way since I wasn't explicit, I'm referring to the ToList() call inside the select -(second to last line) OrderIds = grp.Select(g => g.OrderId).ToList() the other one is fine. It's executed on the results of the SQL query which is perfectly fine, you just can't make calls to C# specific methods within a query that will be executed by the SQL provider.
Your problem is that you select a list in your select statement.
select new OrdersGroup
{
GroupOrderId = grp.Key,
OrderIds = grp.Select(g => g.OrderId).ToList()
/////////////////////////////////////^^^^^^^^^HERE
}
What you need to do is change OrderIds to an IEnumerable<int>, and then get rid of the ToList.

Join vs Navigation property for sub lists in Entity Framework

I have a sql statement like this:
DECLARE #destinations table(destinationId int)
INSERT INTO #destinations
VALUES (414),(416)
SELECT *
FROM GroupOrder grp (NOLOCK)
JOIN DestinationGroupItem destItem (NOLOCK)
ON destItem.GroupOrderId = grp.GroupOrderId
JOIN #destinations dests
ON destItem.DestinationId = dests.destinationId
WHERE OrderId = 5662
I am using entity framework and I am having a hard time getting this query into Linq. (The only reason I wrote the query above was to help me conceptualize what I was looking for.)
I have an IQueryable of GroupOrder entities and a List of integers that are my destinations.
After looking at this I realize that I can probably just do two joins (like my SQL query) and get to what I want.
But it seems a bit odd to do that because a GroupOrder object already has a list of DestinationGroupItem objects on it.
I am a bit confused how to use the Navigation property on the GroupOrder when I have an IQueryable listing of GroupOrders.
Also, if possible, I would like to do this in one trip to the database. (I think I could do a few foreach loops to get this done, but it would not be as efficient as a single IQueryable run to the database.)
NOTE: I prefer fluent linq syntax over the query linq syntax. But beggars can't be choosers so I will take whatever I can get.
If you already have the DestinationGroupItem as a Navigation-property, then you already have your SQL-JOIN equivalent - example. Load the related entities with Include. Use List's Contains extension method to see if the desired DestinationId(s) is(are) hit:
var destinations = new List<int> { 414, 416 };
var query = from order in GroupOrder.Include(o => o.DestinationGroupItem) // this is the join via the navigation property
where order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId)
select order;
// OR
var query = dataContext.GroupOrder
.Include(o => o.DestinationGroupItem)
.Where(order => order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId));

LINQ: Doing an order by!

i have some Linq to Entity code like so:
var tablearows = Context.TableB.Include("TableA").Where(c => c.TableBID == 1).Select(c => c.TableA).ToList();
So i'm returning the results of TableA with TableB.TableBID = 1
That's all good
Now how can I sort TableA by one of its column? There is a many to many relation ship between the two tables
I tried various ways with no look, for example
var tablearows = Context.TableB.Include("TableA").Where(c => c.TableBID == 1).Select(c => c.TableA).OrderBy(p => p.ColumnToSort).ToList();
In the above case when i type "p." i don't have access to the columns from TableA, presumably because it's a collection of TableA objects, not a single row
How about using SelectMany instead of Select :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.SelectMany(c => c.TableA)
.OrderBy(p => p.ColumnToSort)
.ToList();
EDIT :
The expression below returns collection of TableAs -every element of the collection is an instance of TableA collection not TableA instance- (that's why you can't get the properties of the TableA) :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.Select(c => c.TableA);
If we turn the Select to SelectMany, we get the result as one concatenated collection that includes elements :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.SelectMany(c => c.TableA);
Okay, so now I've taken on board that there's a many to many relationship, I think Canavar is right - you want a SelectMany.
Again, that's easier to see in a query expression:
var tableARows = from rowB in Context.TableB.Include("TableA")
where rowB.TableBID == 1
from rowA in rowB.TableA
orderby rowA.ColumnToSort
select rowA;
The reason it didn't work is that you've got a different result type. Previously, you were getting a type like:
List<EntitySet<TableA>>
(I don't know the exact type as I'm not a LINQ to Entities guy, but it would be something like that.)
Now we've flattened all those TableA rows into a single list:
List<TableA>
Now you can't order a sequence of sets by a single column within a row - but you can order a sequence of rows by a column. So basically your intuition in the question was right when you said "presumably because it's a collection of TableA objects, not a single row" - but it wasn't quite clear what you mean by "it".
Now, is that flattening actually appropriate for you? It means you no longer know which B contributed any particular A. Is there only actually one B involved here, so it doesn't matter? If so, there's another option which may even perform better (I really don't know, but you might like to look at the SQL generated in each case and profile it):
var tableARows = Context.TableB.Include("TableA")
.Where(b => b.TableBID == 1)
.Single()
.TableA.OrderBy(a => a.ColumnToSort)
.ToList();
Note that this will fail (or at least would in LINQ to Objects; I don't know exactly what will happen in entities) if there isn't a row in table B with an ID of 1. Basically it selects the single row, then selects all As associated with that row, and orders them.

Categories

Resources