C# Linq Query against sub list - c#

I have a class structure as below whereby I have a list of OrderItems and each item has a list of Discounts
public class Order
{
public IList<OrderItem> OrderItems = new List<OrderItem>();
}
public class OrderItem
{
public IList<Discount> Discounts = new List<Discount>();
}
public class Discount
{
public string Desc { get; set; }
public decimal Amount { get; set; }
public bool IsActive { get; set; }
}
If I want to get a list of all discounts with an IsActive flag of true, how would I do this in a Linq query?
Currently this is what I have but it is very ugly with 2 foreach statements that need to be removed.
IList<Discount> activeDiscounts = new List<Discount>();
foreach (OrderItem item in order.OrderItems)
{
var aDiscounts = from discount in item.Discounts.AsEnumerable()
where discount.IsActive == true
select discount;
foreach (Discount discount in aDiscounts)
{
activeDiscounts.Add(discount);
}
}

Here's a one line linq statement:
order.OrderItems.SelectMany(item => item.Discounts.Where(discount => discount.IsActive));

Related

c# LINQ query to select whole object into new?

Not sure if i worded the question correctly, but what im trying to do is return a new viewmodel with one of the parts being a booking:
public class Booking
{
public int BookingId { get; set; }
public int CustomerId { get; set; }
public Guid UniqueId { get; set; }
public string EventId { get; set; }
public bool IsPaid { get; set; }
public double Price { get; set; }
public DateTime BookingDate { get; set; }
public DateTime DateBooked { get; set; }
[JsonIgnore]
public Customer Customer { get; set; }
[JsonIgnore]
public ICollection<BookingService> BookingServices { get; set; }
[NotMapped]
public IEnumerable<Service> Services { get; set; }
}
and my query is:
var customers = _dbContext.Customers
.Select(c => new CustomerBookingsViewModel
{
Customer = c,
Bookings = c.Bookings.Select(b => new Booking
{
BookingId = b.BookingId,
BookingDate = b.BookingDate,
DateBooked = b.DateBooked,
CustomerId = b.CustomerId,
UniqueId = b.UniqueId,
EventId = b.EventId,
IsPaid = b.IsPaid,
Price = b.Price,
Services = b.BookingServices.Select(s => s.Service)
}),
}
)
.ToList();
What I want to know is how to I select all the booking info into the booking without selecting each part, ie:
BookingId = b.BookingId,
BookingDate = b.BookingDate,
DateBooked = b.DateBooked,
CustomerId = b.CustomerId,
UniqueId = b.UniqueId,
EventId = b.EventId,
IsPaid = b.IsPaid,
Price = b.Price,
Can it be done or because the list of services is inside the booking model it cant?
Thanks.
You could implement the IClonable interface on your class.
public class MyClass : ICloneable
{
public int Id { get; set; }
public object Clone() => MemberwiseClone();
}
Usage:
var list1 = new List<MyClass>
{
new MyClass() { Id = 2 },
new MyClass() { Id = 5 }
};
var list2 = list1.Select(x => (MyClass)x.Clone()).ToList();
list2.First().Id = 10; //list1 won't be affected
You should use AutoMapper here to avoid writing each path.
https://automapper.org/
http://docs.automapper.org/en/stable/Getting-started.html
There is no other way, at least it is not related to LINQ or queries.
The question "How to clone an object" has been answered here:
Creating a copy of an object in C#
There is no LINQ way to do this. I would suggest using custom Attribute marking every property you want to copy. This would help if you want not to copy the whole object but some properties. After marking every property you need you can just set the marked props with reflection from one of the objects to the other.

IQueryable<decimal> to decimal

I have a model class which contains a property ProductPrice of type decimal. I am not able to store an IQueryable type to a property decimal. I have even tried to Convert.ToDecimal but it still showing me the error.
Model - Product
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public int ProductQty { get; set; }
}
Model CartDispay
public class CartDisplay
{
public int ItemId { get; set; }
public String ProductName { get; set; }
public int ProductQty { get; set; }
public decimal ProductPrice { get; set; }
}
Controller
public ActionResult Index()
{
int userId = 3;
var items = _context.Cart.Join(_context.Items, c => c.CartId, i => i.CartId, (c, i) => new { c, i }).Where(c => c.c.UserId == userId).ToList();
foreach(var item in items)
{
CartDisplay display = new CartDisplay();
display.ItemId = item.i.ItemId;
display.ProductName = _context.Product.Where(p => p.ProductId == item.i.ProductId).Select(p => p.ProductName).ToString();
display.ProductPrice = _context.Product.Where(p => p.ProductId == item.i.ProductId).Select(p => p.ProductPrice); ;
display.ProductQty = item.i.ProductQty;
cartView.CartDisplay.Add(display);
}
retu
IQueryable<T> defines a sequence of elements of type T, rather than a single item. It also lets you perform additional querying without bringing the sequence into memory, but that is not important in the context of your question.
Your situation is a lot simpler, though: rather than querying for individual properties, you should query for the whole product by its ID, then take its individual properties, like this:
var prod = _context.Product.SingleOrDefault(p => p.ProductId == item.i.ProductId);
if (prod != null) {
display.ProductName = prod.ProductName
display.ProductPrice = prod.ProductPrice;
display.ProductQty = ...
} else {
// Product with id of item.i.ProductId does not exist
}

How to query a flatten sub collection in RavenDb? Index needed?

I am using RavenDb in C# web project. I have an object that I need to query its child collection with 1 row per child object and some of the root/parent object properties.
Note: This is not the actual design, just simplified for this question.
public class OrderLine
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime? ShipDate { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
The order with the orderlines is one single document. ShipDate will be updated on each line because not all products are always in stock.
I need to be able to create a list of the last 10 products sent with the following columns:
OrderId
Customer
ProductName
ShipDate
This doesn't work because SelectMany is not supported:
var query = from helper in RavenSession.Query<Order>()
.SelectMany(l => l.OrderLines, (order, orderline) =>
new { order, orderline })
select new
{
helper.order.OrderId,
helper.order.CustomerName,
helper.orderline.ProductName,
helper.orderline.ShipDate
};
var result = query.Where(x => x.ShipDate.HasValue)
.OrderByDescending(x => x.ShipDate.Value).Take(10);
I believe the right thing to do isto create an Index that will flatten out the list but I haven't had any success. I don't believe a Map-Reduce situation will work because as I understand it will effectively does a group by which Reduces the number of documents to less rows (in the index). But in this case, I am trying to expand the number of documents to more rows (in the index).
I would rather not put each OrderLine in a separate document but I do not know what my options are.
Since you want to filter and sort by fields in the subclass, you'll need to make sure all the fields you want are indexed and stored.
public class ShippedItemsIndex
: AbstractIndexCreationTask<Order, ShippedItemsIndex.Result>
{
public class Result
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime ShipDate { get; set; }
}
public ShippedItemsIndex()
{
Map = orders =>
from order in orders
from line in order.OrderLines
where line.ShipDate != null
select new
{
order.OrderId,
order.CustomerName,
line.ProductName,
line.Quantity,
line.ShipDate
};
StoreAllFields(FieldStorage.Yes);
}
}
Then you can project from the index into your results.
var query = session.Query<Order, ShippedItemsIndex>()
.ProjectFromIndexFieldsInto<ShippedItemsIndex.Result>()
.OrderByDescending(x => x.ShipDate)
.Take(10);
var results = query.ToList();
Here is a complete test demonstrating.

Linq SelectMany Usage

I unable to come up with a linq query for the following scenario.
public class Product
{
public virtual string ProductName { get; set; }
public virtual IList<SubProduct> SubProducts { get; set; }
}
public class SubProduct
{
public string SubProductName { get; set; }
public int SubProductTypeId { get; set; }
}
public class SubProductType
{
public int SubProductTypeId{ get; set; }
public string Description { get; set; }
}
var productList = List<Product>();
var subProductTypeLlist = List<SubProductType>();
I have a list of products and each product has list of SubProducts. I want to get the query to represent {ProductName, Description}. Please suggest how to write linq query.
Something like this should do the trick:
var result = productList
.SelectMany(p => p.SubProducts
.Select(sp => new { SubProduct = sp, ProductName = p.ProductName }))
.Select(sp =>
new { Description = subProductTypeList
.Single(spt => spt.SubProduct.SubProductTypeId == sp.SubProductTypeId).Description,
ProductName = sp.ProductName })
In the SelectMany, we first do a Select on the internal IEnumerable (IList implements IEnumerable) to convert each SubProduct object to an anonymous class holding the SubProduct object and the ProductName. The SelectMany then converts that to a flat list. We then use Select on that list to create a new anonymous class again, where this time, we grab the Description from subProductTypeList. The result is an IEnumerable of an anonymous class with the members Description and ProductName.

Sorting Collections within and by the Sum of Another Collections

2 classes
public class Student
{
public int StudentID { get; set;}
public string Name { get; set;}
public List<Fee> Fees {get;set;}
}
public class Fee
{
public int FeeID { get; set;}
public decimal FeeAmount { get; set; }
}
let say there are 10 students objects Student[] stud = new Student[10]
if stud[0] has 2 fees ( Fee[2] ) and they are
FeeID=1, FeeAmount=54.23
FeeID=2, FeeAmount=234.98
if stud[1] has 1 fees ( Fee[2] ) and they are
FeeID=1, FeeAmount=9.99
if stud[2] has 3 fees ( Fee[3] ) and they are
FeeID=1, FeeAmount=123.45
FeeID=2, FeeAmount=67.89
FeeID=3, FeeAmount=987.65
I need to sort the Student Collections by TotalAmount(Fee Collection)
TotalAmount of Fee
stud[0] = 54.23+234.98=289.21
stud[1] = =9,99
stud[2] = 123.45+67.89+987.65=1178.99
there for after sorted it should become
stud[0] = 123.45+67.89+987.65=1178.99
stud[1] = 54.23+234.98=289.21
stud[2] = =9,99
It sounds like you just want:
stud = stud.OrderByDescending(x => x.Fees.Sum(fee => fee.FeeAmount)).ToArray();
Gotta love LINQ :)
A couple of things to note:
This will still only calculate the sum of the fees once per student
This will not currently handle null elements. Do you need to? (You seem to have a fixed array size... perhaps use List<Student> instead?)
Unless you actually need it as an array afterwards, just drop the ToArray call. Be aware that it will sort it every time you iterate through it unless you use ToArray or ToList though.
var results = stud.OrderByDescending(s => s.Fees.Sum(f => f.FeeAmount)).ToArray();
A simple Linq query should do the job:
stud =
(from s in stud
orderby s.Fees.Sum(f => f.FeeAmount)
select s)
.ToArray();
var students = new List<Student>() { .. add students here ... };
students.OrderBy(x => x.Fees.Sum(y => y.FeeAmount));
And if you use an old .net framework (without Linq) :
public class Student : IComparable
{
public int StudentID { get; set; }
public string Name { get; set; }
public List<Fee> Fees { get; set; }
private decimal TotalAmount
{
get
{
decimal total = 0;
if (Fees != null)
foreach (var fee in Fees)
total += fee.FeeAmount;
return total;
}
}
public int CompareTo(object obj)
{
//Ascending
//return TotalAmount.CompareTo((obj as Student).TotalAmount);
//Descending
return (obj as Student).TotalAmount.CompareTo(TotalAmount);
}
}
public class Fee
{
public int FeeID { get; set; }
public decimal FeeAmount { get; set; }
}
List<Student> students = new List<Student>();
...
students.Sort();
Linq is better...

Categories

Resources