How do I select two or more values from a collection into a list using a single lambda expression? Here is what I am trying:
List<Prodcut> pds=GetProducts();
List<Product> pdl = new List<Product>();
foreach (Product item in pds)
{
pdl.Add(new Product
{
desc = item.Description,
prodId = Convert.ToInt16(item.pId)
});
}
GetProducts() returns a list of Products that have many (about 21) attributes. The above code does the job but I am trying to create a subset of the product list by extracting just two product attributes (description and productId) using a single lambda expression. How do I accomplish this?
What you want to do is called projection, you want to project each item and turn them into something else.
So you can use a Select:
var pdl = pds.Select(p => new Product
{
desc = p.Description,
prodId = Convert.ToInt16(p.pId)
}).ToList();
Related
I have object CartItem and Product.
I need to make one List<> from these 2 objects.
Tried:
List<(CartItem,Product)> product = new List<(CartItem,Product)>();
Code:
public async Task<IActionResult> Index()
{
var username = User.Identity.Name;
if (username == null)
{
return Login();
}
var user = _context.Users.FirstOrDefault(x => x.Email == username);
List<CartItem> cart = _context.ShoppingCartItems.Where(s => s.IDuser.Equals(user.Id)).ToList();
List<Product> product = new List<Product>();
foreach (var item in cart)
{
product.Add(_context.Product.FirstOrDefault(x => x.id == item.ProductId));
}
return View(user);
}
The correct answer would be to not do that. What would be the expected result for 5 items and 2 products?
The code is loading both CartItem and Product objects from the database using loops. That's very slow as each object requires yet another query. This can be replaced with a single line producing a single SQL query.
If CartItem has Product and User properties (as it should) all the code can be replaced with :
var cart=_context.ShoppingCartItems
.Include(i=>i.Product)
.Where(s=>s.User.Email==userName)
.ToList();
EF Core will generate the SQL query that joins User, CartItem, Product together and returns items and their products, but no User data. The Product data will be available through the CartItem.Product property
What was asked but shouldn't be used
If a List<(CartItem,Product)> is absolutely required (why???) you could use that instead of a List<Product>, and add items inside the loop:
// PLEASE don't do it this way
var dontUseThis = new List<(CartItem,Product?)>();
foreach (var item in cart)
{
var product=_context.Product.FirstOrDefault(x => x.id == item.ProductId);
dontUseThis.Add((item,product));
}
This will result in one extra SQL query per cart item.
Slightly better
A slightly better option would be to generate a WHERE product.ID IN (....) clause, to load all relevant products in a single query. After that the two lists would have to be joined with JOIN.
var productIds=cart.Select(c=>c.ProductId);
var products=_context.Product
.Where(p=>productIds.Contains(p.Id))
.ToList();
var dontUseThis = products.Join(cart,
p => p.Id,
c => c.ProductId,
(c, p) => (c,p))
.ToList();
This will reduce the N+1 queries to 2. It's still slower than letting the database do the work though
First, see the answer of #Panagiotis Kanavos. Aside that, for combining part, you can do this:
List<CartItem> cartItems; // assuming you already have this
List<Product> products; // assuming you already have this
// The combination part
var result = from p in products
join ci in cartItems on p.Id = ci.ProductId // Find out how product and cartItem relates
select new (p,ci);
// Need List?
var resultingList = result.ToList();
You can make a struct and put both variables in there:
struct EntireProduct
{
CartItem cartItem;
Product product;
}
then you can create a list of that struct:
List<EntireProduct> product = new List<EntireProduct>();
I am experimenting with the example CalculatePrice on the Dynamics CRM example page.
And im having a hard time understanding how to get products and bundles in a good manner.
What i wanna try and do is get products from an order with a productstructure attribute and a producttypecode. But it seems whatever i try i get a error The given key was not present in the dictionary.
The query below should look for productID from salesorder based on productID
QueryExpression query = new QueryExpression("salesorderdetail");
query.ColumnSet.AddColumns("quantity", "salesorderispricelocked", "priceperunit", "producttypecode", "_productid_value");
query.Criteria.AddCondition("salesorderid", ConditionOperator.Equal, entity.Id);
QueryExpression query2 = new QueryExpression("product");
query2.ColumnSet.AddColumns("productstructure", "productnumber" , "productid");
query.Criteria.AddCondition("productid", ConditionOperator.Equal, ec.Entities["_productid_value"]);
Then i try to iterate the list of objects to see if they have productstructure and their producttypecode
for (int i = 0; i < ec.Entities.Count; i++)
{
if (ec.Entities[i].GetAttributeValue<int>("producttypecode") == 6)
{ you are a product
if (ec.Entities[i].GetAttributeValue<int>("productstructure") == 3){ you are a bundle
This is the link to the sample code i use:
https://learn.microsoft.com/en-us/dynamics365/customer-engagement/developer/sample-calculate-price-plugin
For starters, the _productid_value notation is the WebAPI's way to access a lookup field. To access the productid using the SDK's late-bound paradigm, use:
myEntity["productid"] or
myEntity.GetAttributeValue<Guid>("productid") or
myEntity.GetAttributeValue<EntityReference>("productid").
Beyond that, since Product is a lookup on the OrderDetail, using a couple LinkEntity objects you could get away with a single query.
I would probably use LINQ and do something like this:
private void getProducts(Guid salesOrderId)
{
using (var context = new Microsoft.Xrm.Sdk.Client.OrganizationServiceContext(svc))
{
var query = from od in context.CreateQuery("salesorderdetail")
join so in context.CreateQuery("salesorder")
on od.GetAttributeValue<Guid>("salesorderid") equals so.GetAttributeValue<Guid>("salesorderid")
join p in context.CreateQuery("product")
on od.GetAttributeValue<Guid>("productid") equals p.GetAttributeValue<Guid>("productid")
where od.GetAttributeValue<Guid>("salesorderid").Equals(salesOrderId)
select new
{
OrderDetailId = od.GetAttributeValue<Guid>("salesorderdetailid"),
ProductId = od.GetAttributeValue<EntityReference>("productid"),
Quantity = od.GetAttributeValue<decimal?>("quantity"),
IsPriceLocked = so.GetAttributeValue<bool?>("ispricelocked"),
PricePerUnit = od.GetAttributeValue<Money>("priceperunit"),
ProductTypeCode = od.GetAttributeValue<OptionSetValue>("producttypecode"),
ProductStructure = p.GetAttributeValue<OptionSetValue>("productstructure"),
ProductNumber = p.GetAttributeValue<string>("productnumber")
};
var results = query.ToList();
var products = results.Where(e => e.ProductStructure.Value == 6).ToList();
var bundles = results.Where(e => e.ProductStructure.Value == 3).ToList();
}
}
Please note that local variables results, products, and bundles are an anonymous type. You can loop through and access the properties of each object, but there's also a strong chance you'd want to cast them into instances of a real class.
Can I include a related object without a query?
With query:
Item item = _db.Items
.Include(i=>i.Supplier)
.Where(....)
Without query:
var item = new Item { Name = "Test", SupplierId = 1 };
item.Include(i => i.Supplier); //something like that...
I don't really understand your question...
First of all Where return multiple objects
IQueryable<Item> items = _db.Items
.Include(i=>i.Supplier)
.Where(....)
Then the result is an IQueryable, the items and suppliers objects are not materialized for the moment. You have to use ToList() for example to enable materialization and query the database.
For the Include method, it's just a join to query Items and Suppliers relationship.
But the Include extension method is only available in IQueryable and not for the entity.
Supplier is normal a simple navigation property on item entity
class Item
{
public virtual Supplier Supplier {get; set;}
}
so you can access using
var item = new Item { Name = "Test", SupplierId = 1 };
item.Supplier = ....
if you want establish a relation with you have to get the supplier
item.Supplier = _db.Suppliers.First(s => s.SupplierId = 1);
Hi I am trying to convert a a LINQ query result into a list of objects and I seem to be doing something wrong because I can not acces the properties of each object.Here is my code:
List<Object> productList = new List<Object>();
var products = (from p in Products
join s in SubCategories on p.SubcatId equals s.SubCatId
join b in Brands on p.BrandId equals b.BrandId
select new
{
Subcategory = s.SubCatName,
Brand = b.BrandName,
p.ProductName,
p.ProductPrice
}).Where(x => x.Subcategory == subcategory);
foreach (var product in products)
{
productList.Add(product);
}
foreach (var produs in productList){
Console.WriteLine(produs.ProductName);
}
When I try to do this I get an error that says:
Object does not contain a definion for ProductName
The same goes for all the other fields two
Aldo if I try and do this:
Console.WriteLine(produs);
I get tables with the data for each field
I have run this for tests on LINQPAD and it also does not work in visual studio.What am I doing wrong?
This is the problem:
List<Object> productList = new List<Object>();
You're declaring it as a List<object>, which means when you later write this:
foreach (var produs in productList)
Then produs is implicitly typed as object.
The simplest approach would be just to use ToList() instead of copying the results to a list yourself:
var products = (from p in Products
join s in SubCategories.Where(x => x.SubCatName == subcategory)
on p.SubcatId equals s.SubCatId
join b in Brands on p.BrandId equals b.BrandId
select new {
Subcategory = s.SubCatName,
Brand = b.BrandName,
p.ProductName,
p.ProductPrice
}).ToList();
Note that I've moved the subcategory name filter as early as possible - there's no need to do it after the join. The ToList() call at the end will mean the result is a List<T> where T is your anonymous type.
Then you can use:
foreach (var product in products)
{
Console.WriteLine(produs.ProductName);
}
use
List<Product> productList = new List<Product>();
instead of
List<Object> productList = new List<Object>();
where Product is the unit of Products
A List of Object means that you only get the methods contained within an object. You should do something like:
List<Product> productList = new List<Product>();
Just as a note you can also do:
productList = products.ToList<Product>();
instead of iterating over the list and adding each element. (Linq does this for you implicitely).
NOTE: This is assuming that you change your query select to select new Product { ... }
This will then allow you to use List<Product> productList so that you can pass your Product list to other methods etc.
I have a very simple linq query that gets data from two datatables (orderHeader & OrderDetails) and joins them together. What I would like to do is take the order items for each order and pass them to a method for processing.
Here is the query:
var results = from orderHeader in dtOrderHeader.AsEnumerable()
join orderDetails in dtOrderDetails.AsEnumerable() on
orderHeader.Field<int>("order_ref") equals
orderDetails.Field<int>("order_id")
select new {
orderID = orderHeader.Field<int>("order_ref"),
orderItem = orderDetails.Field<string>("product_details")
};
What is the best way to iterate the results for each order?
Thanks
This result set contains multiple orders, this would require a nested foreach
foreach (var order in results.Select(r => r.orderID).Distinct()) {
Console.WriteLine("Order: " + order);
Console.WriteLine("Items:");
foreach (var product in results.Where(r => r.orderItem == order)) {
Console.WriteLine(product.orderItem);
}
}