Error in Linq Expression GroupBy in .NET Core 3.1 - linq

After migrating the code from .NET Core 2.1 into 3.1 the following Linq is not working . it shows the InvalidOperationException in LinqExpression - GroupByShaperExpression
Message=The LINQ expression '(GroupByShaperExpression:
KeySelector: new {
DepotNo = (g.DepotNo),
DepotName = (g.DepotName)
},
ElementSelector:(EntityShaperExpression:
EntityType: DepartmentWorkTime
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
)
)
.Select(dd => new {
id = (object)dd.DepotNo + "." + (object)dd.DepartmentID,
title = (object)dd.Depot.DepotNo + "." + dd.Department.DepartmentName
})' 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().
I have two model and another view model
Depot Model
public class Depot
{
[Key]
public int DepotNo { get; set; }
public string DepotName {get;set;}
` }
public class Department
{
[Key]
public int DepartmentID{ get; set; }
public string DepartmentName {get;set;}
}
public class DepartmentWorkTime
{
[Key]
public int Id { get; set; }
public int DepotNo { get; set; }
public int DepartmentID { get; set; }
public DepotModel Depot { get; set; }
public DepartmentModel Department { get; set; }
}
I have the following record in Depot model and Department model
Depot Model
{1, .AAAA Depot},
{2, BBBB Depot},
{4, CCCC Depot},
Department Model
{1, Retail},
{2, Office},
{3, Field Staff},
{4, Warehouse},
DepartmentWorkTime List
{1,1,1, Depot Model ,Department Model},
{2,1,2, Depot Model ,Department Model},
{3,1,4, Depot Model ,Department Model},
{4,2,1, Depot Model ,Department Model},
{5,2,2, Depot Model ,Department Model},
{6,2,3, Depot Model ,Department Model},
{7,4,1, Depot Model ,Department Model},
I am trying to get the result from linq as
[0] = { id = 1, title = "1-AAAA Depot", subs = {System.Collections.Generic.List<<>f__AnonymousType10<string, string>>} }
Subs
[0] { id = "1.1", title = "1.Retail" } <Anonymous Type>
[1] { id = "1.2", title = "1.Office" } <Anonymous Type>
[2] { id = "1.4", title = "1.Warehouse" } <Anonymous Type>
[1] = { id = 2, title = "2-BBBB Depot", subs = {System.Collections.Generic.List<<>f__AnonymousType10<string, string>>} }
Subs
[0] { id = "2.1", title = "2.Retail" } <Anonymous Type>
[1] { id = "2.2", title = "2.Office" } <Anonymous Type>
[2] { id = "2.3", title = "2.Field Staff" } <Anonymous Type>
[2] ={ id = 3, title = "4-CCCC Depot", subs = {System.Collections.Generic.List<<>f__AnonymousType10<string, string>>} }
Subs
[0] { id = "4.1", title = "4.Retail" } <Anonymous Type>
For that purpose I have written the linq as given below , it is working in .Net Core 2.1 but not 3.1
**public JsonResult GetDepotDepartemntsForMap()
{
dynamic mappingList = new List<DepotMapModel>();
mappingList = _unitOfWork.Department.GetDepotWithDepartment();
return Json(mappingList);
}
public class DepartmentMapModel
{
public string id { get; set; }
public string title { get; set; }
}
public class DepotMapModel
{
public string id { get; set; }
public string title { get; set; }
public List<DepartmentMapModel> subs { get; set; }
}
public dynamic GetDepotWithDepartment()
{
var list = goContext.goDepartmentWorkTime.
GroupBy(d => new { d.DepotNo, d.Depot.DepotName })
.Select(g => new
{
id = g.Key.DepotNo,
title = g.Key.DepotName,
subs = g.Select(dd => new
{
id = dd.DepotNo + "." + dd.DepartmentID,
title = dd.Depot.DepotNo + "." + dd.Department.DepartmentName
}).ToList()
}).ToList();
return list;
}**

in the new EF core version, it only allows the last Select() call to be evaluated on the client.
You can get all the data by calling goDepartmentWorkTime, so you do not need to use groupby, check this:
var list = (from d in _context.goDepartmentWorkTime
select new
{
id = d.DepotNo,
title = d.Depot.DepotName,
subs = new
{
id = d.DepotNo + "." + d.DepartmentID,
title = d.Depot.DepotNo + "." + d.Department.DepartmentName
}
}).ToList();
Result:

Related

EF Core - many queries sent to database for subquery

Using EF Core 2.2.2, I have a table in my database which is used to store notes for many other tables. In other words, it's sortof like a detail table in a master-detail relationship, but with multiple master tables. Consider this simplified EF Model:
public class Person
{
public Guid PersonID { get; set; }
public string Name { set; set; }
}
public class InvoiceItem
{
public Guid InvoiceItemID { get; set; }
public Guid InvoiceID { get; set; }
public string Description { get; set; }
}
public class Invoice
{
public Guid InvoiceID { get; set; }
public int InvoiceNumber { get; set; }
public List<Item> Items { get; set; }
}
public class Notes
{
public Guid NoteID { get; set; }
public Guid NoteParentID { get; set; }
public DateTime NoteDate { get; set; }
public string Note { get; set; }
}
In this case, Notes can store Person notes or Invoice notes (or InvoiceItem notes, though let's just say that the UI doesn't support that).
I have query methods set up like this:
public IQueryable<PersonDTO> GetPersonQuery()
{
return from p in Context.People
select new PersonDTO
{
PersonID = p.PersonID,
Name = p.Name
};
}
public List<PersonDTO> GetPeople()
{
return (from p in GetPersonQuery()
return p).ToList();
}
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber
};
}
public List<InvoiceDTO> GetInvoices()
{
return (from i in GetInvoiceQuery()
return i).ToList();
}
These all work as expected. Now, let's say I add InvoiceItems to the Invoice query, like this:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList()
};
}
That also works great, and issues just a couple queries. However, the following:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in Context.Notes
where i.InvoiceID = n.NoteParentID
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};
}
sends a separate query to the Note table for each Invoice row in the Invoice table. So, if there are 1,000 invoices in the Invoice table, this is sending something like 1,001 queries to the database.
It appears that the Items subquery does not have the same issue because there is an explicit relationship between Invoices and Items, whereas there isn't a specific relationship between Invoices and Notes (because not all notes are related to invoices).
Is there a way to rewrite that final query, such that it will not send a separate note query for every invoice in the table?
The problem is indeed the correlated subquery versus collection navigation property. EF Core query translator still has issues processing such subqueries, which are in fact logical collection navigation properties and should have been processed in a similar fashion.
Interestingly, simulating collection navigation property with intermediate projection (let operator in LINQ query syntax) seems to fix the issue:
var query =
from i in Context.Invoices
let i_Notes = Context.Notes.Where(n => i.InvoiceID == n.NoteParentID) // <--
select new InvoiceDTO
{
InvoiceID = i.InvoiceID,
InvoiceNumber = i.InvoiceNumber,
Items = (from ii in i.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in i_Notes // <--
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};

Dynamic LINQ: Comparing Nested Data With Parent Property

I've a class with following structure:
public class BestWayContext
{
public Preference Preference { get; set; }
public DateTime DueDate { get; set; }
public List<ServiceRate> ServiceRate { get; set; }
}
public class ServiceRate
{
public int Id { get; set; }
public string Carrier { get; set; }
public string Service { get; set; }
public decimal Rate { get; set; }
public DateTime DeliveryDate { get; set; }
}
and I've dynamic linq expression string
"Preference != null && ServiceRate.Any(Carrier == Preference.Carrier)"
and I want to convert above string in Dynamic LINQ as follows:
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, null).Compile();
But it showing following error:
Please correct me what am I doing wrong?
It looks like you wanted to do something like this:
var bwc = new BestWayContext
{
Preference = new Preference { Carrier = "test" },
DueDate = DateTime.Now,
ServiceRate = new List<ServiceRate>
{
new ServiceRate
{
Carrier = "test",
DeliveryDate = DateTime.Now,
Id = 2,
Rate = 100,
Service = "testService"
}
}
};
string condition = "Preference != null && ServiceRate.Any(Carrier == #0)";
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, bwc.Preference.Carrier).Compile();
bool res = expression(bwc); // true
bwc.ServiceRate.First().Carrier = "test1"; // just for testing this -> there is only one so I've used first
res = expression(bwc); // false
You want to use Preference which belong to BestWayContext but you didn't tell the compiler about that. If i write your expression on Linq i will do as follows:
[List of BestWayContext].Where(f => f.Preference != null && f.ServiceRate.Where(g => g.Carrier == f.Preference.Carrier)
);
As you see i specified to use Preference of BestWayContext.

Group records by id

I am getting records like this returned from a stored procedure:
Id FullName Review Rating
---------------------------------------------
1 john sk 4.5
1 john hhh 3.5
1 john jhj 1.5
2 rig www 3.5
2 rig eee 1.5
This is my query which return records like above:
var empDetails = context.Database.SqlQuery<SearchWorkerDetail>("exec SearchWorkerDetail #param1", new SqlParameter("param1", searchKeyword) )
.Select( d => new
{
id = d.Id,
FullName = d.FullName,
Email = d.Email,
ServiceDescription = d.ServiceDescription,
Skills = d.Skills,
Name = d.Name,
r = d.Review,
averagerating = d.Rating
}).ToList();
Now I want to group the records by Id and want to select data.
Expected output:
SearchWorkerDetail=
[0]:{
Id:1
FullName:John
Email:john#yahoo.com
ReviewList:
{
[0]: Review=sk
rating=4.5
[1]: Review=hhh
rating=3.5
[2]: Review=jhj
rating=1.5
}
[1]:{
Id:2
FullName:rig
Email:rig#yahoo.com
ReviewList:
{
[0]: Review=www
rating=3.5
[1]: Review=eee
rating=1.5
}
}
For corresponding id I want list of reviews (1 to many relation between worker and review)
My class structure:
[DataContract]
public class SearchWorkerDetail
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FullName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string ServiceDescription { get; set; }
[DataMember]
public string Skills { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Review { get; set; }
[DataMember]
public Nullable<decimal> Rating { get; set; }
[DataMember]
public List<ReviewModel> RatingList { get; set; }
}
My query is like this:
var data = empDetails.GroupBy(m=>m.id)
.Select(g => new SearchWorkerDetail
{
id =g.Key,
FullName = g.FullName,
Reviewlist =
}
).ToList();
Here it is not allowing me to select FullName and other (eg: Email, skillDescription, Skills etc)
How do I do this?
There is no FullName property on g because it's a group not a single object. However you can get the FullName of first record in the group then create a ReviewList for each record in the group by using another Select:
empDetails.GroupBy(m => m.id)
.Select(g => new SearchWorkerDetail
{
id = g.Key,
FullName = g.First().FullName,
Reviewlist= g.Select(x => new ReviewModel
{
Review = x.Review,
rating = x.Rating
}.ToList()
}).ToList();
Or you can also group by based on FullName:
empDetails.GroupBy(m => new { m.id, m.FullName })
.Select(g => new SearchWorkerDetail
{
id = g.Key.id,
FullName = g.Key.FullName,
/* rest is the same */
}).ToList();

How to get ASP.Net Web API and OData to bind a string value as a key?

I'm going through a short Web Api + OData tutorial from asp.net: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/getting-started-with-odata-in-web-api/create-a-read-only-odata-endpoint.
I downloaded the example project, and it works. But then I started playing around with the Product model that they use in the example. I added a new property to act as a key of type string instead of an integer key.
The new Product.cs:
public class Product
{
public string stringKey { get; set; }
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
The modified controller:
public class ProductsController : EntitySetController<Product, string>
{
static List<Product> products = new List<Product>()
{
new Product() { stringKey = "one", ID = 1, Name = "Hat", Price = 15, Category = "Apparel" },
new Product() { stringKey = "two", ID = 2, Name = "Socks", Price = 5, Category = "Apparel" },
new Product() { stringKey = "three", ID = 3, Name = "Scarf", Price = 12, Category = "Apparel" },
new Product() { stringKey = "four", ID = 4, Name = "Yo-yo", Price = 4.95M, Category = "Toys" },
new Product() { stringKey = "five", ID = 5, Name = "Puzzle", Price = 8, Category = "Toys" },
};
[Queryable]
public override IQueryable<Product> Get()
{
return products.AsQueryable();
}
protected override Product GetEntityByKey(string key)
{
return products.FirstOrDefault(p => p.stringKey == key);
}
}
The trouble is that when I go to /odata/Products(one) the string "one" is not bound to the key argument in the GetEntityByKey(string key) action. However, when I browse to odata/Products(1) then "1" does get bound to the key argument.
How can I get a string with text values to bind correctly, instead of just binding strings with numerical values?
Update
I forgot to include the WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");
Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute("ODataRoute", "odata", model);
}
}
I noticed that the path /odata/Products(0011-1100) would only bind "0011" as the string key.
After some playing around with it, I've found that the following path works as I had hoped:
/odata/Products('one')
It appears the single quotes are required to read the entire string within the parentheses.

Get Count from entity framework

Quite new to EF, basically i want to convert this SQL query:
SELECT
PSKU.ProductSKUID,
PSKU.ProductSKUName,
W.WarehouseID,
W.WarehouseName,
SA.SystemAreaName,
COUNT(SLI.ProductSKUID) AS QTY
FROM dbo.StockLineItem AS SLI INNER JOIN
dbo.ProductSKU AS PSKU ON PSKU.ProductSKUID = SLI.ProductSKUID INNER JOIN
dbo.Warehouse AS W ON W.WarehouseID = SLI.WarehouseID INNER JOIN
dbo.SystemArea AS SA ON SA.SystemAreaID = SLI.SystemAreaID
WHERE (SA.SystemAreaID = 1)
AND W.WarehouseID = #WarehouseID
GROUP BY PSKU.ProductSKUID, PSKU.ProductSKUName, W.WarehouseName, SA.SystemAreaName, W.WarehouseID
To an effective EF statement. This is what i Have so far, my Model class and the method:
[Serializable]
public class StockReturnMethod
{
public int ProductSKUID { get; set; }
public int WarehouseID { get; set; }
public int LotID { get; set; }
public string LotName { get; set; }
public int AreaID { get; set; }
public string AreaName { get; set; }
public int BinID { get; set; }
public string BinName { get; set; }
}
public class DALStockMovement
{
scmEntitiesPrimaryCon entities = new scmEntitiesPrimaryCon();
public List<AvailibleStock> AvailibleStockQty(int warehouseID)
{
var rows = (from PLA in entities.ProductLocationAssignments
from W in entities.Warehouses
from SLI in entities.StockLineItems
from SA in entities.SystemAreas
from PSKU in entities.ProductSKUs
where W.WarehouseID == warehouseID
select new AvailibleStock() { WarehouseID = W.WarehouseID, ProductSKUID = PSKU.ProductSKUID, ProductSKUName = PSKU.ProductSKUName, WarehouseName = W.WarehouseName, Status = SA.SystemAreaName, QtyUnassigned = SLI.ProductSKUID }).ToList();
return rows;
}
Any Advice to get this to an Effective EF Statement would be appreciated
I actually used this tool called Linqer, since I had the SQL
I just popped it into that tool and it generated the Linq for me.
Here is what came out:
var SKUStock = (from sli in entities.StockLineItems
where
sli.SystemArea.SystemAreaID == 1 &&
sli.WarehouseID == warehouseID
group new { sli.ProductSKU, sli.Warehouse, sli.SystemArea, sli } by new
{
ProductSKUID = (System.Int32?)sli.ProductSKU.ProductSKUID,
sli.ProductSKU.ProductSKUName,
sli.Warehouse.WarehouseName,
sli.SystemArea.SystemAreaName,
WarehouseID = (System.Int32?)sli.Warehouse.WarehouseID
} into g
select new AvailibleStock()
{
ProductSKUID = (int)(System.Int32?)g.Key.ProductSKUID,
ProductSKUName = g.Key.ProductSKUName,
WarehouseID = (int)(System.Int32?)g.Key.WarehouseID,
WarehouseName = g.Key.WarehouseName,
Status = g.Key.SystemAreaName,
QtyUnassigned = (int)(Int64?)g.Count(p => p.sli.ProductSKUID != null)
}).ToList();
return SKUStock;
It returns exactly what i need :).

Resources