Raven DB: How to create "UniqueVisitorCount by date" index - c#

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.

Related

Filter Object List with string array list C# lambda expression

I had tried a similar SO answer here which not worked and might be missing something in my case.
Background:
I'm trying to pull a list of Trade Instruments from an external API ( around 8k records ) from which I need around 10 only. So trying to filter it as below , but the filter results are 0 .
Model:
public class Trade
{
public int ID { get; set; }
public string Scrip { get; set; }
public int Quantity { get; set; }
}
Filtering:
List<Trade> trades;
using (StreamReader sr = new StreamReader(Server.MapPath("~/Utils/trades.json")))
{
trades = JsonConvert.DeserializeObject<List<Trade>>(sr.ReadToEnd());
}
List<Instrument> instruments = GetInstruments(Exchange: "NY");// count 8k
var result = instruments.Where(x => trades.Any(n => x.Name.Contains(n.Scrip))); //count 0
Also, tried to fetch the Scrip names from the trades list as string array and used for filtering which also didn't work.
Please advise and thanks in advance.
Thanks guys for the help , was a silly mistake
changed Contains to Equals and its working as expected.
instruments.Where(x => trades.Any(n => x.Name.Equals(n.Scrip)));

dynamically creating linq with mongodb

I've just started using mongodb in c# and it's great however I'm struggling to understand how i could dynamically create a linq query to pass to mongodb.
Situation: I have a file that has some general properties filename, filesize ect, one of these properties is metadata, which is a list of fields with values. the user will be able to specify the search criteria dynamically and so i cant hard code this query.
My Object for completeness:
public class asset
{
public ObjectId Id { get; set; }
public string filename { get; set; }
public int filesize { get; set; }
public List<shortmetadata> metadata { get; set; }
}
public class shortmetadata
{
public string id { get; set; }
public string value { get; set; }
}
My current code which is manually setting the search criteria and returns any asset that has "hello" or "world" in the metadata value field:
MongoClient client = new MongoClient();
var db = client.GetDatabase("Test");
var collection = db.GetCollection<asset>("assets");
var assets = collection.AsQueryable().Where(i =>
i.metadata.Any(m => m.value.Contains("hello")) ||
i.metadata.Any(m => m.value.Contains("world"))
);
What i would like to be able to do is dynamically create the query based on the users selection (don't have this yet as want to get it working in code first!)
Any help would be great.
If, for example, you had a Dictionary<string, string> containing the name value to search for keyed by the name of the meta item you could build your IQueryable<Asset> up in a loop like this
var query = collection.AsQueryable();
//Non-meta properties
query = query.Where(a => a.SomeNonMetaProperty == "Something");
//And now meta properties
foreach(var keyAndValue in someDictionary)
{
query = query.Where(m =>
m.Name == keyAndValue.Key
&& m.Value == keyAndValue.Value;
}
Slazure lets you create dynamic Linq queries at runtime since its predicates are string literals.
PM> Install-Package Slazure.MongoDB
// C# example: Build a document query that return employees that has a salary greater than $40k/year using a dynamic LINQ query filter.
dynamic storage = new QueryableStorage<DynDocument>("mongodb://user:pass#example.org/MongoDBExample");
QueryableCollection<DynDocument> employeesCollection = storage.Employees;
var employeeQuery = employeesCollection
// Query for salary greater than $40k and born later than early '95.
.Where("Salary > 40000 and Birthdate >= DateTime(1995,15,3)")
// Projection and aliasing.
.Select("new(_id as Email, Birthdate, Name, Timestamp as RegisteredDate)")
// Order result set by birthdate descending.
.OrderBy("Birthdate desc")
// Paging: Skip the first 5 and retrieve only 5.
.Skip(5).Take(5)
// Group result set on Birthdate and then on Name.
.GroupBy("Birthdate", "Name");
// Use a dynamic type so that we can get access to the document's dynamic properties
foreach (dynamic employee in employeeQuery)
{
// Show some information about the employee
Console.WriteLine("The employee '{0}' was employed {1} and was born in {2}.",
employee.Email, employee.RegisteredDate, employee.Birthdate.Year);
}
It also supports substitution values which makes your predicate code look cleaner.
// C# example: Query the storage for employee that earn less than $60k/yr and that are born before the millennium.
var amount = 60000;
var employeeQuery = employeesTable.Where("Salary > #0 and Timestamp <= #1", amount, new DateTime(2000, 1, 1));

LINQ SUM data based on Array Results

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!

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.

get list from based on another list

I asked this question earlier, but I over simplified it, and I still don't know how to do it in a better way than I'm doing (for).
I got two lists.
One, a list of facebook friends, a simple object, consider facebook.id the only property.
Second, a list of users, it's a more complex object, because each user gets a lists of providers inside (facebook, twitter, etc), but the providers list can be null, and if not null, not necessarily the provider is a facebook one. So:
public class EFacebook
{
public long Id { get; set; }
}
public class EUser
{
public long Id { get; set; }
/// <summary>
/// Nullable
/// </summary>
public List<EProvider> EProviders { get; set; }
}
public class EProvider
{
public enum EnumProviderType
{
Facebook = 2,
Twitter = 3
}
public EnumProviderType ProviderType { get; set; }
public string Id { get; set; }
}
What I need is to filter the facebook list to get all the facebook friends that are users and get all the facebook friends that are not users.
Suppose that List<EFacebook> fbList is the first list and List<EUser> usersList is the second list.
You can do something like this:
fbList.Where(x=>usersList.Select(x=>x.Id).Contains(x.Id)) ==> this will return the list of facebook entities that are users.
The second list is the difference between this fbList and this one.
Let me know if I understood the question correctly!
Tamash
Assuming this:
List<EFacebook> listEFacebookFriends = new List<EFacebook>();
List<EUser> listEUsers = new List<EUser>();
Then you can get a list of all Facebook friends that are users here:
var listEUsersOnFacebook = from user in listEUsers
let fbProviders =
from provider in user.EProviders
where provider.ProviderType == EProvider.EnumProviderType.Facebook
select provider.Id
where fbProviders.Count() > 0
select user.Id;
// this next call will get facebook friends that are users
var friendsOnFacebook = listEFacebookFriends.Where(x =>
listEUsersOnFacebook.Contains(x.Id));
And here you can get your Facebook friends that are NOT users:
var listEUsersNotOnFacebook = from user in listEUsers
let fbProviders =
from provider in user.EProviders
where provider.ProviderType == EProvider.EnumProviderType.Facebook
select provider.Id
where fbProviders.Count() == 0
select user.Id;
// this call will get facebook friends that are not users
var friendsNotOnFacebook = listEFacebookFriends.Where(x =>
listEUsersNotOnFacebook.Contains(x.Id));

Categories