LINQ SUM data based on Array Results - c#

I'm stuck on this issue. I know it can be done nicely with LINQ (I don't want to use multiple foreach loops), but I simply cannot get it to work. Here is the problem:
I have two classes:
Class Invoice
public class Invoice
{
public int InvoiceID { get; set; }
public string Name { get; set; }
public DateTime DueDate { get; set; }
public Invoice(int ID, string sName, DateTime dt_Date)
{
this.InvoiceID = ID;
this.Name = sName;
this.DueDate = dt_Date;
}
}
and Class Activity
public class Activity
{
public int ActivityID { get; set; }
public int InvoiceID { get; set; }
public int Count { get; set; }
public double Price { get; set; }
public Activity(int ID, int InvoiceID, int iCount, double dPrice)
{
this.ActivityID = ID;
this.InvoiceID = InvoiceID;
this.Count = iCount;
this.Price = dPrice;
}
}
The logic is that Each Invoice contains multiple Activities. E.g. you have a bill from a shop (Invoice), which includes items such as bread, butter, milk (Activities).
What I want to do is that based on user selected Date, I want to return total amount paid (basically I want to perform SUM of all bills from specific Date period).
Here is what I have:
//user selected DateTime - for the sake of test we make it current day
DateTime selectedDate = DateTime.Now;
//Retrieve invocies that match user selected Date
var retrievedInvoices = invList
.Where(n => n.DueDate.ToShortDateString() == selectedDate.ToShortDateString());
This is fine, as we have retrieved list of all Invoices based on desired Date. But now? I tried something as following:
//now make SUM of activities that match retrievedInvoices -> look at their
//ID's and if match is found, then multiply price x count
double dResult = invActivity
.Where(n => retrievedInvoices.Where(x=>x.InvoiceID == n.InvoiceID))
.Sum(n => n.Price * n.Count);
But it is not working. I am not that proficient in LINQ so there might be more elegant way of doing it (maybe in one line of code) but I don't know.
Could you guys help me with this one, please?
EDIT:
Interesting thing to note also for others that might be looking at this thread: I have first tried to use List in order to retrieve my list of invoices that match specific time period (DateTime_From and DateTime_To); but it behaved strangely as sometimes it worker correctly, sometimes not (even though the code was correct). After I changed List <Invoice> retrievedInvoice to var retrievedInvoice it suddenly worked without a problem. I don't understand why this is so, but I will definitely be more aware next time of what type do I use.
Thanks again folks!

Your code looks fine, but try some changes like this:
use .Date to compare just Dates without Time (hours, minutes, etc..) and select just eh InvoiceID proeprty into a array
var retrievedInvoices = invList.Where(n => n.DueDate.Date == selectedDate.Date)
.Select(x => x.InvoiceID);
use .Contains() to check the condition which return a bool value.
double dResult = invActivity
.Where(n => retrievedInvoices.Contains(n.InvoiceID))
.Sum(n => n.Price * n.Count);
Some changes was suggested by Tuespetre user in comments bellow!

Related

Using Contains() in a Realm query

Let's say we have a realm results taken with
RealmDb.All<Entry>();
Then I want to do some search over those results using not yet supported techniques, like StartsWith on a function return or on a property which is not mapped in realm etc, so I get a subset
IEnumerable<Entry> subset = bgHaystack;
var results = subset.Where(entry => entry.Content.ToLower().StartsWith(needle));
To get somehow these as part of RealmResults, I extract the entry ids like this:
List<int> Ids = new List<int>();
foreach (Entry entry in entries)
{
Ids.Add(entry.Id);
}
return Ids;
and finally I want to return a subset of RealmResults (not IEnumerable) of only those Entries that contain those ids, how can I do that? IDE says the Contains method is not supported.
Can I use some kind of predicate or a comparer for that?
Entry is my model class
using System.ComponentModel.DataAnnotations.Schema;
using Realms;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System;
namespace Data.Models
{
[Table("entry")]
public class Entry : RealmObject
{
public class EntryType
{
public const byte Word = 1;
public const byte Phrase = 2;
public const byte Text = 3;
};
[Key]
[PrimaryKey]
[Column("entry_id")]
public int Id { get; set; }
[Column("user_id")]
public int UserId { get; set; }
[Column("source_id")]
public int SourceId { get; set; }
[Indexed]
[Column("type")]
public byte Type { get; set; }
[Column("rate")]
public int Rate { get; set; }
[Column("created_at")]
public string CreatedAt { get; set; }
[Column("updated_at")]
public string UpdatedAt { get; set; }
[NotMapped]
public Phrase Phrase { get; set; }
[NotMapped]
public Word Word { get; set; }
[NotMapped]
public Text Text { get; set; }
[NotMapped]
public IList<Translation> Translations { get; }
[NotMapped]
public string Content
{
get {
switch (Type)
{
case EntryType.Phrase:
return Phrase?.Content;
case EntryType.Word:
return Word?.Content;
case EntryType.Text:
return Text?.Content;
}
return "";
}
}
}
}
According to the documentation, Realm .NET supports LINQ, so that's promising. In your specific example, you indicate that StartsWith isn't supported, but I see that on the above page, specifically here.
Now, your example makes clear that Entry is a RealmObject, so it's not clear where you'd possibly get a RealmResult from (nor does their documentation on that page mention a RealmResult). Specifically, the home page indicates that you're really only going to ever work with Realm, RealmObject and Transaction, so I'm going to just assume that you meant that you'll need a resulting RealmObject per their examples.
The way you presently have your data object set up, you're rather stuck calling it like you are (though if I could make a recommendation to simplify it a little bit:
var entries = RealmDb.All<Entry>().ToList();
var results = entries.Where(entry => entry.Content.ToLower().StartsWith(needle));
var ids = results.Select(a => a.Id).ToList();
Now, your big issue with just combining the filter predicate in line 2 with the end of line 1: Content itself is marked with a [NotMapped] attribute. Per the documentation again:
As a general rule, you can only create predicates with conditions that
rely on data in Realm. Imagine a class
class Person : RealmObject
{
// Persisted properties
public string FirstName { get; set; }
public string LastName { get; set; }
// Non-persisted property
public string FullName => FirstName + " " + LastName;
}
Given this class, you can create queries with conditions that apply to
the FirstName and LastName properties but not to the FullName
property. Likewise, properties with the [Ignored] attribute cannot be
used.
Because you're using [NotMapped], I've got to believe that's going to behave similarly to [Ignored] and further, because it's just a computed value, it's not something that Realm is going to be able to process as part of the query - it simply doesn't know it because you didn't map it to the information Realm is storing. Rather, you'll have to compute the Content property when you've actually got the instances of your Entry objects to enumerate through.
Similarly, I expect you'll have issues pulling values from Phrase, Word and Text since they're also not mapped, and thus not stored in the record within Realm (unless you're populating those in code you didn't post before executing your Where filter).
As such, you might instead consider storing separate records as a PhraseEntry, WordEntry, and TextEntry so you can indeed perform exactly that filter and execute it on Realm. What if you instead used the following?
public class Entry : RealmObject
{
[Key]
[PrimaryKey]
[Column("entry_id")]
public int Id { get; set; }
[Column("user_id")]
public int UserId { get; set; }
[Column("source_id")]
public int SourceId { get; set; }
[Column("rate")]
public int Rate { get; set; }
[Column("created_at")]
public string CreatedAt { get; set; }
[Column("updated_at")]
public string UpdatedAt { get; set; }
[Column("content")]
public string Content { get; set; }
[NotMapped]
public IList<Translation> Translations { get; }
}
[Table("wordEntry")]
public class WordEntry : Entry
{
}
[Table("phraseEntry")]
public class PhraseEntry : Entry
{
}
[Table("textEntry")]
public class TextEntry : Entry
{
}
And now, you can offload the filtering to Realm:
var wordEntries = RealmDb.All<WordEntry>.Where(entry =>
entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();
var phraseEntries = RealmDb.All<PhraseEntry>.Where(entry => entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();
var textEntries = RealmDb.All<TextEntry>.Where(entry => entry.Content.StartsWith(needle, StringComparison.OrdinalIgnoreCase)).ToList();
var entries = new List<Entry>();
entries.AddRange(wordEntries);
entries.AddRange(phraseEntries);
entries.AddRange(textEntries);
var ids = entries.Select(entry => entry.Id).ToList();
It's not quite as brief as storing it all in one table, but I'm not immediately seeing any Realm documentation that indicates support for executing the same query against multiple tables simultaneously, so at least this would allow you to leave the filtering to the database and work against a more limited subset of values locally.
Finally, so we have all that and I missed your final question up top. You indicate that you want to return a subset of your entries based on some collection of ids you create. In the logic you provide, you're retrieving all the Id properties in all your results, so there's really no further subset to pull.
That said, let's assume you have a separate list of ids that for whatever complicated reason, you were only able to derive after retrieving the list of Entry types from above (themselves all PhraseEntry, WordEntry or TextEntry objects).
At this point, since you've already pulled all the values from Realm and have them locally, just execute another Where statement against them. Because a List implements IEnumerable, you can thus execute the LINQ locally without any of the Realm restrictions:
var myLimitedIdSet = new List<int>()
{
10, 15, 20, 25 //Really complicated logic to narrow these down locally
};
var resultingEntries = entries.Where(entry => myLimitedIdSet.Contains(entry.Id)).ToList();
And you're set. You'll have only those entries that match the IDs listed in myLimitedIdSet.
Edit to address comment
You see this error because of the detail provided at the top of this page in the documentation. Specifically (and adapting to your code):
The first statement gives you a new instance of Entry of a class that implements IQueryable... This is standard LINQ implementation - you get an object representing the query. The query doesn't do anything until you made a further call that needs to iterate or count the results.
Your error is then derived by taking the result from RealmDb.All<Entry>() and trying to cast it to an IEnumerable<Entry> to operate against it as though you have local data. Until you call ToList() onRealmDb.All` you simply have a LINQ representation of what the call will be, not the data itself. As such, when you further refine your results with a Where statement, you're actually adding that to a narrowed version of the IQueryable statement, which will also fail because you lack the appropriate mapping in the Realm dataset.
To skip the optimization I provided above, the following should resolve your issue here:
var bgHaystack = realm.All<Entry>().ToList(); //Now you have local data
var results = bgHaystack.Where(entry => entry.Content.ToLower().StartsWith(needle));
Unfortunately, given your provided code, I don't expect that you'll see any matches here unless needle is an empty string. Not only is your Content property not part of the Realm data and you thus cannot filter on it within Realm, but neither are your Phrase, Word or Text properties mapped either. As a result, you will only ever see an empty string when getting your Content value.
You can further refine the results variable above to yield only those instances with a provided ID as you see fit with normal LINQ (as again, you'll have pulled the data from Realm in the first line).
var limitedIds = new List<int>{10, 20, 30};
var resultsLimitedById = results.Select(a => limitedIds.Contains(a.Id)).ToList();
I've updated my examples above to reflect the use of ToList() in the appropriate places as well.

Creating index in ravendb with Nodatime's LocalDate

I hope somebody can help me.
I'm using Ravendb with the Nodatime bundles, and so far I didn't have any problem with it until I wanted to use some nodatime methods during index creation.
Product:
public class Product {
public string Id { get; set; }
public LocalDate ReleasedDate { get; set; }
// and more properties...
}
My index creation (AbstractMultiMapIndexCreationTask):
AddMap<Product>(Product =>
from product in products
let dateTimeUTc = DateTimeZone.Utc.AtStartOfDay(product.ReleasedDate)
let timeTicks = dateTimeUTc.ToInstant().Ticks
let hash = $"{product.Id}|{timeTicks}"
select new IndexEntry
{
Hash = hash,
Usage = 0
});
With that the index is being generated, the ticks are there, nothing is wrong except that I get an indexing error for each index record in ravendb:
The best overloaded method match for 'NodaTime.DateTimeZone.AtStartOfDay(NodaTime.LocalDate)' has some invalid arguments
Does anybody know why is that?

Change the type of one of the objects property and sort it using LINQ

I want to sort the List where the objects properties are of string type.
One of the property is a time of string type, and when i try to sort it sorts like below.
1:12, 13:24, 19:56, 2:15, 26:34, 8:42.
Here the sorting is happening on string basis.
Now i want to convert that sting to double (1.12, 13.24, 19.56, 2.15, 26.34, 8.42) and sort it. Then populate the data by replacing the '.' with ':'.
I tried some thing like below, but still the sorting is happening on string basis.
public class Model
{
public string Duration { get; set; }
public string Dose { get; set; }
}
List<Model> lsModelData = new List<Model>();
//Added some model objects here
// query for sorting the lsModelData by time.
var sortedList = lsModelData.OrderBy(a => Convert.ToDouble(a.Duration.Replace(":", ".")));
I am trying to replace the time ":" with "." and then convert that to double to perform the sort operation.
Can any one please correct this statement to work this sorting properly.
If you want to sort data according to duration try this. its tested surely works for you.
public class Models
{
public string Duration { get; set; }
public string Dose { get; set; }
}
List<Models> lstModels = new List<Models>();
lstModels.Add(new Models { Duration = "101:12" });
lstModels.Add(new Models { Duration = "13:24" });
lstModels.Add(new Models { Duration = "19:56" });
List<Models> sortedList = (from models in lstModels
select new Models
{
Dose = models.Dose,
Duration = models.Duration.Replace(':','.')})
.ToList()
.OrderBy(x=>Convert.ToDouble(x.Duration))
.ToList();
I'm not sure what you really want, but if you want to return only the duration, then select it after sort
var sortedList = lsModelData.OrderBy(a => Convert.ToDouble(a.Duration.Replace(":", "."))).Select(a=> a.Duration).ToList();
or
var sortedList = lsModelData..Select(a=> a.Duration).OrderBy(a => Convert.ToDouble(a.Replace(":", "."))).ToList();
In cases like this it works best to order by length and then by content:
var sortedList = lsModelData.OrderBy(a => a.Duration.Length)
.ThenBy(a => a.Duration)
Converting database data before sorting (or filtering) always makes queries inefficient because indexes can't be used anymore.

Linq lambda using where

I am still learning lambda and Linq from Microsoft site and trying to write some simple example myself to get a deeper understanding of the cool stuff. The more I learn the more I find that stuff interesting but the learning curve is steep. Once again, I will need some more assistance.
Basically, I have class called item where it has properties NodeID, Weight and Category.
I have also a class called Recipient which represent recipient receiving items.
I have also a 2 dimensional boolean table that shows the interaction of one item against the other. If an item1 with ID NodeID1 is not supposed to have with item2 with ID Node2 then the table[Node1][Node2] should have a value true.
What I am trying to find out is the list of recipients that receive stuff that should not be receiving together, in other word stuff that has value true in the table.
public class Recipient
{
private Dictionary<int,item> _itemsReceivedList=new Dictionary<int,item>(); //itemID
private int _recipientID;
public int RecipientID{ get; set; }
public List<int> getListItemInCategory(int category)
{
return _itemsReceivedList.Where(x => x.Value.Category == category).Select(x => x.Value.NodeID).ToList();
}
}
public class item
{
public int NodeID { get; set; }
public int Weight { get; set; }
public int Category { get; set; }
}
In my main program:
private bool[][] prohibitedMatrix; //prohibitedMatrix[NodeID1][NodeID2]=true means it is prohibited to have Item NodeID1 and NodeID2 together
private Dictionary<int,Recipient> recipients = new Dictionary<int,Recipient>();
private Dictionary<int, item> items = new Dictionary<int,item>();
given an item with NodeID1, find recipients that has x in _itemReceivedList so that prohibitedMatrix[x.NodeID][NodeID1]= true
recipients.Where(x=>x.Value.getListItemInCategory(items[NodeID].Category)
&& "The NodeID in listItemInCategory and NodeID1 is not
true)
.Select(x=>x.Value.RecipientID)
Thank you for your help!
To have a one-liner this should work:
var result = recipients
.Values
.Select(r => new
{
RecipientID = r.RecipientID,
Items = r.getListItemInCategory(items[NodeID].Category)
})
.Where(ri => ri.Items.Any(i => prohibitedMatrix[i.NodeID][NodeID]))
.Select(ri => ri.RecipientID);
or this:
var result = recipients
.Values
.Where(r => r
.getListItemInCategory(items[NodeID].Category)
.Any(i => prohibitedMatrix[i.NodeID][NodeID]))
.Select(r => r.RecipientID);
However better to introduce some utility functions here and partition this. Or use plain-old foreach.
I think I found answer to my own question. I may or may not be correct. Please let me know if I am wrong.
This is what I think:
recipients.Where((x,y)=>x.Value.getListItemInCategory(items[NodeID1].Category).Contains(y) && prohibitedMatrix[y][NodeID1]).Select(x=>x.Value.RecipientID).ToList()

Raven DB: How to create "UniqueVisitorCount by date" index

I have an application to track the page visits for a website.
Here's my model:
public class VisitSession {
public string SessionId { get; set; }
public DateTime StartTime { get; set; }
public string UniqueVisitorId { get; set; }
public IList<PageVisit> PageVisits { get; set; }
}
When a visitor go to the website, a visit session starts. One visit session has many page visits. The tracker will write a UniqueVisitorId (GUID) cookie when the first time a visitor go to the website. So we are able to know if a visitor is returning visitor.
Now, I want to know how many unique visitors visited the website in a date range. That is, I want to display a table in our webpage like this;
Date | Unique Visitors Count
------------+-----------------------
2012-05-01 | 100
2012-05-02 | 1000
2012-05-03 | 120
I want to create an index to do this in RavenDB. But I don't know how to write the Map/Reduce query. I though it can be like this:
public class UniqueVisitor_ByDate : AbstractIndexCreationTask<VisitSession, UniqueVisitorByDate>
{
public UniqueVisitor_ByDate()
{
Map = sessions => from s in sessions
select new
{
s.StartTime.Date,
s.UniqueVisitorId
};
Reduce = results => from result in results
group result by result.Date into g
select new
{
Date = g.Key,
UniqueVisitorCount = g.Distinct()
};
}
}
But it's not working. In Ayende's e-book, I know that the result of Map function should be same as the result of Reduce function. So how can I write the correct map/reduce functions?
This index should do what you want:
public class UniqueVisitor_ByDate : AbstractIndexCreationTask<VisitSession, UniqueVisitorByDate>
{
public UniqueVisitor_ByDate()
{
Map = sessions =>
from s in sessions
select new {
s.StartTime.Date,
s.UniqueVisitorId,
Count = 1,
};
Reduce = results =>
from result in results
group result by result.Date
into g
select new UniqueVisitorByDate {
Date = g.Key,
Count = g.Select(x => x.UniqueVisitorId).Distinct().Count(),
UniqueVisitorId = g.FirstOrDefault().UniqueVisitorId,
};
}
}
Note that it requires the extra 'UniqueVisitorId' property in the 'reduce' and the 'count' property in the map, but you can just ignore those.

Categories