Linq groupBy, Sum and orderBy - c#

Still learning Linq here. So I've got a class that looks like this (comes from a db table):
class FundRaisingData
{
public double funds { get; set; }
public DateTime date { get; set; }
public string agentsGender { get; set; }
}
What I need to do is transform this to list of anonymous objects that looks something like this (it will be transformed and returned as JSON later ). I know this is not an anonymous object, but it will give you some idea of what I'm trying to do:
class groupedFunds
{
public string gender { get; set; }
public List<double> fundsRaised { get; set; }
}
So I need to figure out how I can sum the funds for each year in the right order (2010-2014).
Eventually it should look like this in JSON:
ListOfGroupedFunds[
{
"gender" : "Female",
"fundsRaised" : [2000, 2500, 3000]
},
{
"gender" : "Male",
"fundsRaised": [4300,2300,3100]
}
];
So fundsRaised[0] would correspond to 2012, fundsRaised[1] would correspond to 2013, etc. but not actually say "2013" anywhere in the object.
I've read a ton of articles today on Linq and searched through similar StackOverflow articles but I still just can't quite figure it out. Any help in pointing me in the right direction would be awesome.
-- Edit 2 (changing code more closely match solution) --
I think the code by Mike worked well, but because I'm not sure how many years there will be in advance I've modified it slightly:
var results = data.GroupBy(g=> Gender = g.agentsGender)
.Select(g=> new
{
gender = g.Key
years = g.GroupBy(y=> y.Date.Year)
.OrderBy(y=> y.Key)
.Select(y=> y.Sum(z=> z.funds))
.ToArray()
})
.ToArray();
Is there anything wrong about the above? It seems to work but I'm open to better solutions of course.

Querying for any number of years is just another sub GroupBy() in your gender group.
var results = data.GroupBy(g=> Gender = g.agentsGender)
.Select(g=> new
{
gender = g.Key
years = g.GroupBy(y=> y.Date.Year)
.OrderBy(y=> y.Key)
.Select(y=> y.Sum(y.funds))
.ToArray()
})
.ToArray();

Thins LINQ statement will Group By Gender, then get the sum of funds per year as a List.
var query = from d in data
group d by d.agentsGender into g
select new { gender = g.Key, fundsRaised = new List<double> {
g.Where(f => f.date.Year == 2012).Sum(f => f.funds),
g.Where(f => f.date.Year == 2013).Sum(f => f.funds),
g.Where(f => f.date.Year == 2014).Sum(f => f.funds),
}};

Related

How do I group on one of two possible fields using LINQ?

I am trying to get the latest contact with a given user, grouped by user:
public class ChatMessage
{
public string SentTo { get; set; }
public string SentFrom { get; set; }
public string MessageBody { get; set; }
public string SendDate { get; set; }
}
The user's contact info could either be in SentTo or SentFrom.
List<ChatMessage> ecml = new List<ChatMessage>();
var q = ecml.OrderByDescending(m => m.SendDate).First();
would give me the latest message, but I need the last message per user.
The closest solution I could find was LINQ Max Date with group by, but I cant seem to figure out the correct syntax. I would rather not create multiple List objects if I don't have to.
If the user's info is in SentTo, my info will be in SentFrom, and vice-versa, so I do have some way of checking where the user's data is.
Did I mention I was very new to LINQ? Any help would be greatly appreciated.
Since you need to interpret each record twice - i.e. as a SentTo and a SentFrom, the query becomes a bit tricky:
var res = ecml
.SelectMany(m => new[] {
new { User = m.SentFrom, m.SendDate }
, new { User = m.SentTo, m.SendDate }
})
.GroupBy(p => p.User)
.Select(g => new {
User = g.Key
, Last = g.OrderByDescending(m => m.SendDate).First()
});
The key trick is in SelectMany, which makes each ChatMessage item into two anonymous items - one that pairs up the SentFrom user with SendDate, and one that pairs up the SentTo user with the same date.
Once you have both records in an enumerable, the rest is straightforward: you group by the user, and then apply the query from your post to each group.
It should be pretty easy, look at this code:
string username = "John";
var q = ecml.Where(i=>i.SentFrom == username || i.SentTo == username).OrderByDescending(m => m.SendDate).First();
It simply filter your collection be choosing items which either SentFrom or SentTo is equal to username.

Entity Framework, DateAdd - manipulate dates upon select

Is it possible to manipulate dates in an EF query while maintaining the flexibility of wildcard select?
In old school SQL you can do this
SELECT ID, Name,
DATEADD (minute, 30, [StartTime]) AS [StartTime]
FROM Titles
I know you can to the same in EF with
var items = context.Titles.Select(n => new {
ID = n.ID,
Name= n.Name,
StartTime = System.Data.Objects.EntityFunctions.AddMinutes(n.StartTime, 30)
});
The challenge for me is that I'd like to not have to specify all properties.
Is it possible to do something like
var items = context.Titles.SomethingSomething(n =>
LinqToUpdateOnly 'StartTime' property);
?
You could create a class that holds the original entity plus any additional properties you want and then create a translation method that projects from your entity to the new class. It is not as flexible as you are looking for, but you can do this:
Custom class
public class TitlePoco
{
public Title Title { get; set; }
public DateTime StartTime { get; set; }
}
A single point that handles all the translations you want
public IQueryable<TitlePoco> TranslateTitles(IQueryable<Title> query)
{
return query.Select(n => new TitlePoco {
Title = n,
StartTime = System.Data.Objects.EntityFunctions.AddMinutes(n.StartTime, 30)
});
}
Then you call your method like this
var items = TranslateTitles(context.Titles.Where(x => ...));
How about this for old school?
var items = context.Titles.ToList();
foreach(var title in items) StartTime = StartTime.AddMinutes(30);

Indexing wish and trade lists in RavenDB

I've tried many different strategies for indexing my data but can't seem to figure it out by myself.
I'm building a database over users and their games. The users can supply the database with games they own and would like to trade as well as a list of games they would like to have:
public class Member : EntityBase
{
public List<Game> TradeList { get; set; }
public List<Game> WishList { get; set; }
}
I'm trying to create and index I can query in the form of "Give me a list of all games (with corresponding members) which have games in their TradeList matching my WishList as well as having games in their WishList matching my TradeList".. and of course, myself excluded.
I tried creating a MultiMapIndex:
public class TradingIndex : AbstractMultiMapIndexCreationTask<TradingIndex.Result>
{
public enum ListType
{
Wishlist,
Tradelist
}
public class Result
{
public string Game { get; set; }
public string Member { get; set; }
public ListType List { get; set; }
}
public TradingIndex()
{
AddMap<Member>(members => from member in members
from game in member.TradeList
select new Result()
{
Game = game.Id,
Member = member.Id,
List = ListType.Tradelist
});
AddMap<Member>(members => from member in members
from game in member.WishList
select new Result()
{
Game = game.Id,
Member = member.Id,
List = ListType.Wishlist
});
}
}
And then querying it like this:
db.Query<TradingIndex.Result, TradingIndex>()
.Where(g =>
(g.Game.In(gamesIWant) && g.List == TradingIndex.ListType.Tradelist)
&&
(g.Game.In(gamesITrade) && g.List == TradingIndex.ListType.Wishlist)
&&
g.Member != me.Id
)
But I can't get that to work. I've also looked at Map/Reduce, but the problem I have seem to be getting RavenDB to give me the correct result type.
I hope you get what I'm trying to do and can give me some hints on what to look into.
First, you'll need to make sure that you store the fields you are indexing. This is required so you can get the index results back, instead of the documents that matched the index.
Add this to the bottom of your index definition:
StoreAllFields(FieldStorage.Yes);
Or if you want to be more verbose, (perhaps your index is doing other things also):
Store(x => x.Game, FieldStorage.Yes);
Store(x => x.Member, FieldStorage.Yes);
Store(x => x.List, FieldStorage.Yes);
When you query this, you'll need to tell Raven to send you back the index entries, by using ProjectFromIndexFieldsInto as described here.
Next, you need to realize that you aren't creating any single index entry that will match your query. The multimap index is creating separate entries in the index for each map. If you want to combine them in your results, you'll need to use an intersection query.
Putting this together, your query should look like this:
var q = session.Query<TradingIndex.Result, TradingIndex>()
.Where(g => g.Game.In(gamesIWant) &&
g.List == TradingIndex.ListType.Tradelist &&
g.Member != me.Id)
.Intersect()
.Where(g => g.Game.In(gamesITrade) &&
g.List == TradingIndex.ListType.Wishlist &&
g.Member != me.Id)
.ProjectFromIndexFieldsInto<TradingIndex.Result>();
Full test in this GIST.

How to calculate the final price of each product within a List<T>?

The goal
Calculate the final price of each product within a List<T> of C#.
The problem
I need to calculate price * quantity of a simple product and return the result to the view.
Look this syntax:
var productPrice = productsPrices.Find(x => x.productId == 1);
productPrice.finalProductPrice =
(productPrice.promotionalProductPrice != 0 ?
productPrice.promotionalProductPrice :
productPrice.originalProductPrice)
* sessionProducts.Find(x => x.id == 1).quantity;
Just before of code fragment passed above, there is a string that stores the ids of the products, see:
string ids = string.Join(", ", sessionProducts.Select(x => x.id));
And I think that I need it to set a value to finalProductPrice of each product of my productsPrices list, but I don't know how I can perform this.
What I thought about this subject:
I had thought that I could use something like this:
productsPrices.Find(x => x.productId == productsPrices.Contains(ids))
.finalProductPrice =
(productsPrices.Find(x => productsPrices.Contains(ids))
.promotionalProductPrice != 0 ?
productsPrices.Find(x => productsPrices.Contains(ids))
.promotionalProductPrice :
productsPrices.Find(x => productsPrices.Contains(ids))
.originalProductPrice) *
sessionProducts.Find(x => x.id == 1).quantity;
But, of course, no success — .Contains() doesn't work with strings.
My real question
How can I define the right finalProductPrice for each product that my productsPrices list has? How can I pass each id within string ids [...] to x.productId == something?
Hints will be welcome (E.g. don't use .Find(), use .Select() instead!)
Spotlight code
Follow my ActionResult that sends all the necessary information to my view:
Compare.cs:
public ActionResult Compare()
{
List<getMarkets_Result> markets = Markets.Build();
SessionStore sessionStore = new SessionStore();
List<getSpecificProductToShoppingList_Result> sessionProducts =
sessionStore.Get("ProductsSummary", "generic");
string ids = string.Join(", ", sessionProducts.Select(x => x.id));
List<getProductsPrices_Result> productsPrices = Products.PricesList(ids);
ListComparisonViewModel listComparisonViewModel =
new ListComparisonViewModel
{
Markets = markets,
SessionProducts = sessionStore.Get("ProductsSummary", "generic"),
ProductsPrices = productsPrices
};
return View(listComparisonViewModel);
}
If necessary, the context of my productsPrice list:
public partial class getProductsPrices_Result
{
public decimal originalProductPrice { get; set; }
public decimal promotionalProductPrice { get; set; }
public decimal finalProductPrice { get; set; }
public string productName { get; set; }
public string marketName { get; set; }
public int productId { get; set; }
public int marketId { get; set; }
}
And I made a model called Products(.cs) to perform the logic behind of the products. The first (and single) method that this model has is CalculateFinalPrice():
public decimal CalculateProductPriceByQuantity(decimal price, int quantity)
{
return price * quantity;
}
I think you should conciser creating a model that represents your data in a way that will make your business problem easier to solve. In other words, make the code work to solve you solution.
Firstly the product should not know about the quantity. Assuming this works like a cart, the product should only know about itself. So the implementation of finalProductPrice should be much easier:
public decimal finalProductPrice
{
get
{
return (promotionalProductPrice != 0 ?
promotionalProductPrice :
originalProductPrice);
}
}
When working with your products, create a class and contain them in that to calculate the quality and total price:
public class ProductCollection
{
public IEnumerable<getProductsPrices_Result> Products { get; set; }
public int Quantity { get { return Products.Count; } }
public decimal TotalPrice
{
get
{
return Products.Sum(p => p.finalProductPrice );
}
}
public ProductCollection(IEnumerable<getProductsPrices_Result> products)
{
this.Products = products;
}
}
Now your viewmodel can look something like this:
ListComparisonViewModel
{
Markets = markets,
Products = new ProductCollection(sessionStore.Get("ProductsSummary", "generic"))
};
One important note I would like to make here is this helps to reduce your session access surface area. Session can be "accessed by anyone" and thus can become discombobulated and cause lots of issues down the road. In almost all scenarios fewer hands in the session cookie jar the better. (fewer keys as well as fewer class types and instances)
You've got a lot of issues here. Firstly, your properties should be PascalCase, not camelCase. Secondly, your class is poorly named. getProductsPrices_Result shouldn't be prefixed with a verb, and it should be PascalCase with no underscores.
Aside from that, you have a lot of redundant code. The FinalProductPrice doesn't sound like an appropriate property for a Product type.
To answer the question about getting a collection of products that match a collection of ids, you can use LINQ:
string[] ids = ...;
List<Product> products = ...;
List<Product> matchingProducts =
products.Where(p => ids.Any(i => i == p.Id)).ToList();
question is a bit fuzzy but looks like you may be looking for something like following
var res = (from p in products
from s in sessionproducts
where p.id == s.id
select new Product() { id = p.id, finalprice = s.qty * (p.promoprice > 0 ? p.promoprice : p.price) })
.ToList();
I would double check your class design.
I would make promotionalPrice
nullable, what if you need price = 0 for a promotion?
Consider making this an extension method for the collection of products references in session.
With what I have at hand:
foreach(var product in sessionProducts){
var productPrice == productPrices.FirstOrDefault(p=>p.ProductId == product.id);
var price = 0;
if (productPrice != null)
price = productPrice.promotionalPrice == 0 ? productPrice.originalPrice : productPrice.promotionalPrice;
product.finalPrice = product.Qty * price;
}

RavenDB index for nested query

I'm pretty new to RavenDB and am struggling to find a solution to the following:
I have a collection called ServiceCalls that look like this:
public class ServiceCall
{
public int ID { get; set; }
public string IncidentNumber { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
public DateTime ReportedDateTime { get; set; }
public string Block { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
}
I have an index named ServiceCalls/CallsByCategory that looks like this:
Map = docs => from doc in docs
select new
{
Category = doc.Category,
CategoryCount = 1,
ServiceCalls = doc,
};
Reduce = results => from result in results
group result by result.Category into g
select new
{
Category = g.Key,
CategoryCount = g.Count(),
ServiceCalls = g.Select(i => i.ServiceCalls)
};
So the output is:
public class ServiceCallsByCategory
{
public string Category { get; set; }
public int CategoryCount { get; set; }
public IEnumerable<ServiceCall> ServiceCalls { get; set; }
}
using this query everything works as it should
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory") select i
Where I am absolutely lost is writing an index that would allow me to query by ReportedDateTime. Something that would allow me to do this:
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
where i.ServiceCalls.Any(x=>x.ReportedDateTime >= new DateTime(2012,10,1))
select i
Any guidance would be MUCH appreciated.
A few things,
You can't have a .Count() method in your reduce clause. If you look closely, you will find your counts are wrong. As of build 2151, this will actually throw an exception. Instead, you want CategoryCount = g.Sum(x => x.CategoryCount)
You always want the structure of the map to match the structure of the reduce. If you're going to build a list of things, then you should map a single element array of each thing, and use .SelectMany() in the reduce step. The way you have it now only works due to a quirk that will probably be fixed at some point.
By building the result as a list of ServiceCalls, you are copying the entire document into the index storage. Not only is that inefficient, but it's unnecessary. You would do better keeping a list of just the ids. Raven has an .Include() method that you can use if you need to retrieve the full document. The main advantage here is that you are guaranteed to have the most current data for each item you get back, even if your index results are still stale.
Putting all three together, the correct index would be:
public class ServiceCallsByCategory
{
public string Category { get; set; }
public int CategoryCount { get; set; }
public int[] ServiceCallIds { get; set; }
}
public class ServiceCalls_CallsByCategory : AbstractIndexCreationTask<ServiceCall, ServiceCallsByCategory>
{
public ServiceCalls_CallsByCategory()
{
Map = docs => from doc in docs
select new {
Category = doc.Category,
CategoryCount = 1,
ServiceCallIds = new[] { doc.ID },
};
Reduce = results => from result in results
group result by result.Category
into g
select new {
Category = g.Key,
CategoryCount = g.Sum(x => x.CategoryCount),
ServiceCallIds = g.SelectMany(i => i.ServiceCallIds)
};
}
}
Querying it with includes, would look like this:
var q = session.Query<ServiceCallsByCategory, ServiceCalls_CallsByCategory>()
.Include<ServiceCallsByCategory, ServiceCall>(x => x.ServiceCallIds);
When you need a document, you still load it with session.Load<ServiceCall>(id) but Raven will not have to make a round trip back to the server to get it.
NOW - that doesn't address your question about how to filter the results by date. For that, you really need to think about what you are trying to accomplish. All of the above would assume that you really want every service call shown for each category at once. Most of the time, that's not going to be practical because you want to paginate results. You probably DON'T want to even use what I've described above. I am making some grand assumptions here, but most of the time one would filter by category, not group by it.
Let's say you had an index that just counts the categories (the above index without the list of service calls). You might use that to display an overview screen. But you wouldn't be interested in the documents that were in each category until you clicked one and drilled into a details screen. At that point, you know which category you're in, and you can filter by it and reduce to a date range without a static index:
var q = session.Query<ServiceCall>().Where(x=> x.Category == category && x.ReportedDateTime >= datetime)
If I am wrong and you really DO need to show all documents from all categories, grouped by category, and filtered by date, then you are going to have to adopt an advanced technique like the one I described in this other StackOverflow answer. If this is really what you need, let me know in comments and I'll see if i can write it for you. You will need Raven 2.0 to make it work.
Also - be very careful about what you are storing for ReportedDateTime. If you are going to be doing any comparisons at all, you need to understand the difference between calendar time and instantaneous time. Calendar time has quirks like daylight savings transitions, time zone differences, and more. Instantaneous time tracks the moment something happened, regardless of who's asking. You probably want instantaneous time for your usage, which means either using a UTC DateTime, or switching to DateTimeOffset which will let you represent instantaneous time without losing the local contextual value.
Update
I experimented with trying to build an index that would use that technique I described to let you have all results in your category groups but still filter by date. Unfortunately, it's just not possible. You would have to have all ServiceCalls grouped together in the original document and express it in the Map. It doesn't work the same way at all if you have to Reduce first. So you really should just consider simple query for ServiceCalls once you are in a specific Category.
Could you add ReportedDateTime to the Map and aggregate it in the Reduce? If you only care about the max per category, something like this should be sufficient.
Map = docs => from doc in docs
select new
{
Category = doc.Category,
CategoryCount = 1,
ServiceCalls = doc,
ReportedDateTime
};
Reduce = results => from result in results
group result by result.Category into g
select new
{
Category = g.Key,
CategoryCount = g.Sum(x => x.CategoryCount),
ServiceCalls = g.Select(i => i.ServiceCalls)
ReportedDateTime = g.Max(rdt => rdt.ReportedDateTime)
};
You could then query it just based on the aggregated ReportedDateTime:
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
where i.ReportedDateTime >= new DateTime(2012,10,1)
select i

Categories