How to call a stored JavaScript function to add a field to objects returned by a query using linq and c# driver - mongodb

I have this query:
var professionalProfilesQuery =
(from profileCollection in _mongoContext.Database.GetCollection<Profile>("profiles").AsQueryable()
join userCollection in _mongoContext.Database.GetCollection<User>("users").AsQueryable()
on profileCollection.UserId equals userCollection.Id.Value
into users
orderby profileCollection.Name
select new ProfessionalProfile
{
Id = profileCollection.Id,
Name = profileCollection.Name,
UserId = profileCollection.UserId,
IsConfirmed = users.First().IsConfirmed,
})
.Where(p => p.IsConfirmed);
And then I have a stored javascript function in DB that calculates a "Score" for each ProfessionalProfile according to data in several collections (I need to sort results later according to that score). Something like:
function candidateScore(objectWithDataToCalculateScore, professionalProfile) {
var score = 0;
//Calculate score here
return score;
}
Is it possible to add a field to data returned by the query with the calculated score? How can I call the stored javascript function to do so? I'd like to do something like:
var professionalProfilesQuery =
(from profileCollection in _mongoContext.Database.GetCollection<Profile>("profiles").AsQueryable()
join userCollection in _mongoContext.Database.GetCollection<User>("users").AsQueryable()
on profileCollection.UserId equals userCollection.Id.Value
into users
orderby profileCollection.Name
select new ProfessionalProfile
{
Id = profileCollection.Id,
Name = profileCollection.Name,
UserId = profileCollection.UserId,
IsConfirmed = users.First().IsConfirmed,
Score = ***CallToCandidateScoreFunctionHere...***
})
.Where(p => p.IsConfirmed);
If that's not possible or it's not a good way to do it, would you suggest another approach instead of stored javascript function?
Thanks.
EDIT: After reading comments from Neil Lunn, I'll explain better the calculation I need.
This is an example of the class that models the collection that would be the first parameter in the previous candidateScore javascript function:
public class ProfessionalProfileRequirements
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
public string Role { get; set; }
public string Province { get; set; }
public IEnumerable<string> LanguagesRequired { get; set; }
public IEnumerable<string> SkillsRequired { get; set; }
}
And this the javascript function again with some of the calculations (it's quite complex, so I simplified it. I hope it's enough to show what I need):
function candidateScore(professionalProfileRequirements, professionalProfile) {
var score = 0;
if (professionalProfileRequirements.Province === professionalProfile.Province) {
score += 1000;
}
if (professionalProfileRequirements.Role && professionalProfile.Roles && professionalProfile.ExperienceLevel){
if (professionalProfile.Roles.includes(professionalProfileRequirements.Role)) {
switch (professionalProfile.ExperienceLevel) {
case 'ExperienceLevel_NoExperience':
score += 1;
break;
case 'ExperienceLevel_LessThanOneYear':
score += 2;
break;
case 'ExperienceLevel_BetweenOneAndThreeYears':
score += 4;
break;
case 'ExperienceLevel_BetweenThreeAndFiveYears':
score += 6;
break;
case 'ExperienceLevel_MoreThan5Years':
score += 10;
break;
}
}
}
return score;
}
I don't know how I can do that kind of calculation using "aggregate". Any help or any other approach I could follow would be appreciated.

Related

Hungarian method - 3 choices assignment

I have a question about the hungarian method for assigment problems.
In the examples I found from the hungarian method you have 1 to n prefereces.
So at the moment, we have in the school the task to create a programm where you have a school class (1 to n students). The students take exactly ONE gift to the class. So (1 to amount of students )
After that every student hast exactly three CHOICES(to pick one gift, e.g. gift 1, gift 5, gift 9) and our programm is supossed to output the best assignment for the class.
But as mentioned before the examples we found about the hungarian methods have 1- to n preferences. And we need exactly three.
How would we solve this specific task ?
Is the hungarian method still the best way to solve this task or should we look at another algorithm ?
I just saw your last question get closed without an answer.
I've implemented a practical way to solve this problem in C#, assuming the wishes are given in a csv file of the following format:
2,10,6
2,7,3
4,7,1
...
The number of columns, i.e. the number of preferences, doesn't matter for my implementation.
Obviously, there's room for performance improvements, but I chose to keep it more readable for the sake of the answer.
Here are the classes used in the code below:
public class Student
{
public int StudentNumber { get; set; }
public List<WishVote> WishVotes { get; set; }
}
public class WishVote
{
public int WishNumber { get; set; }
public int Order { get; set; }
public string Id { get; set; }
}
public class WishVoteResult
{
public int WishNumber { get; set; }
// wish order
// vote count for that order
// wish number
public List<Tuple<int, int, int>> Assignments { get; set; }
public int TotalVoteCount { get; set; }
}
And here is the code you could run in Main to output the wishes' numbers alongside the total number of votes in descending order:
var lines = File.ReadAllLines("wishes.csv").ToList();
int studentNumber = lines.Count;
var students = new List<Student>();
int currentLine = 0;
lines.ForEach(l =>
{
currentLine++;
var wishVotes = new List<WishVote>();
int wishOrder = 0;
l.Split(',').ToList().ForEach(w =>
{
wishOrder++;
wishVotes.Add(new WishVote
{
Id = Guid.NewGuid().ToString(),
Order = wishOrder,
WishNumber = Convert.ToInt32(w)
});
});
students.Add(new Student
{
StudentNumber = currentLine,
WishVotes = wishVotes
});
});
var allWishVotes = students.SelectMany(s => s.WishVotes).ToList();
List<int> uniqueWishes = allWishVotes.Select(w => w.WishNumber).Distinct().ToList();
var wishVoteResults = new List<WishVoteResult>();
// assuming every row in the file has the same number of columns
int orderCount = students.First().WishVotes.Max(w => w.Order);
uniqueWishes.ForEach(uw =>
{
var wishVoteResult = new WishVoteResult
{
WishNumber = uw,
TotalVoteCount = allWishVotes.Where(w => w.WishNumber == uw).Count(),
Assignments = new List<Tuple<int, int, int>>()
};
for(int i = 1; i <= orderCount; i++)
{
wishVoteResult.Assignments.Add(new Tuple<int, int, int>(i, allWishVotes.Where(w => w.Order == i && w.WishNumber == uw).Count(), uw));
}
wishVoteResults.Add(wishVoteResult);
});
var assignments = wishVoteResults.SelectMany(w => w.Assignments).OrderByDescending(a => a.Item2).ThenBy(a => a.Item1).ToList();
Console.WriteLine("Wish {wishNumber}: {voteCount} {wishOrder}");
foreach (var assignment in assignments)
{
Console.WriteLine($"Wish number {assignment.Item3}: {assignment.Item2} {assignment.Item1}");
}
Console.WriteLine("Finished.");
Effectively, what we're doing is creating all the possible assignments and then just sorting accordingly.

Compute totals of invoice server side when invoice/invoiceLine changed

I use Breeze with Durandal (still 1.2) and I am facing a problem which I haven't found an easy solution for. I have 2 entities: Invoice & InvoiceLine like described below:
public class Invoice
{
[Key]
public int Id { get; set; }
public string InvoiceNumber { get; set; }
public string Comment { get; set; }
public double? TotalExclVAT { get; set; }
public double? TotalInclVAT { get; set; }
public double? TotalVAT { get; set; }
public bool? WithoutVAT { get; set; }
public virtual List<InvoiceLine> Lines { get; set; }
}
public class InvoiceLine
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public double VatPercent { get; set; }
public double Amount { get; set; }
public int InvoiceId { get; set; }
public virtual Invoice Invoice { get; set; }
}
I need to compute the totals of the invoice (TotalExclVAT, TotalInclVAT, TotalVAT) in 2 cases:
Whenever someone adds/modifies an invoice line.
Whenever someone changes the flag WithoutVAT on the invoice.
I don't think this is a good idea to perform this compute client side. Performing this server side is better for security reasons mainly.
My first thought was to do the job in the BeforeSaveEntity of Invoice & InvoiceLine.
Here is what i did:
public bool BeforeSaveEntity(EntityState entityState, EntityInfo entityInfo)
{
var invoice = entityInfo.Entity as Invoice;
...
ComputeTotal(entityInfo, invoice);
}
private void ComputeTotal(EntityInfo entityInfo, Invoice invoice)
{
var query = Context.InvoiceLines.Where(x => x.invoiceId == invoice.Id).AsEnumerable();
double totalExclVAT = 0;
double totalVAT = 0;
int percent = 0;
foreach (var line in query.ToList())
{
totalExclVAT = ...
totalVAT = ...
}
entityInfo.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
entityInfo.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
entityInfo.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
accounting.TotalExclVAT = totalExclVAT;
accounting.TotalInclVAT = totalExclVAT + totalVAT;
accounting.TotalVAT = totalVAT;
}
The same kind of thing is done for the invoice line. As you can see in the ComputeTotal function, I perform a query to get invoice lines from DB then computing totals and saving results in the invoice.
It doesn't work quite well: in case of adding a new line on my invoice, performing a query on my DB doesn't get this added line! Because it is not already stored in DB.
It would have been easier to proceed client side but I don't think this is a good idea... is it?
So I am sure there is another way of doing but I don't find it myself.
Any help is greathly appreciated.
UPDATE
Below is my first shot with this problem.
public Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
{
List<EntityInfo> invoices;
List<EntityInfo> invoiceLines;
EntityInfo ei;
if (!saveMap.TryGetValue(typeof(InvoiceLine), out invoiceLines))
{
// if we fall here it means no invoice lines exists in the saveMap
}
if (!saveMap.TryGetValue(typeof(Invoice), out invoices))
{
// if we fall here it means no invoices exists in the saveMap
// >> getting the invoice from DB and add it to the map
using (var dc = new BreezeContext())
{
int invoiceId = ((InvoiceLine)invoiceLines[0].Entity).InvoiceId;
EFContextProvider<BreezeContext> cp = new EFContextProvider<BreezeContext>();
var acc = dc.Invoices.Where(x => x.Id == invoiceId).FirstOrDefault();
ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);
invoices = new List<EntityInfo>();
saveMap.Add(typeof(Invoice), invoices);
invoices.Add(ei);
}
}
// There is only 1 invoice at a time in the saveMap
Invoice invoice = (Invoice)invoices[0].Entity;
ei = invoices[0];
Dictionary<int, InvoiceLine> hashset = new Dictionary<int, InvoiceLine>();
// Retrieving values of invoice lines from database (server side)
using (var dc = new BreezeContext())
{
var linesServerSide = dc.InvoiceLines.Where(x => x.InvoiceId == invoice.Id).AsEnumerable();
foreach (var elm in linesServerSide)
{
hashset.Add(elm.Id, elm);
}
}
// Retrieving values of invoice lines from modified lines (client side)
foreach (var entityInfo in invoiceLines)
{
InvoiceLine entity = (InvoiceLine)entityInfo.Entity;
switch (entityInfo.EntityState)
{
case Breeze.WebApi.EntityState.Added:
hashset.Add(entity.Id, entity);
break;
case Breeze.WebApi.EntityState.Deleted:
hashset.Remove(entity.Id);
break;
case Breeze.WebApi.EntityState.Modified:
hashset.Remove(entity.Id);
hashset.Add(entity.Id, entity);
break;
}
}
// Computing totals based on my hashset
double totalExclVAT = 0;
double totalInclVAT = 0;
double totalVAT = 0;
foreach (var elm in hashset)
{
InvoiceLine line = elm.Value;
totalExclVAT += line.Amount;
totalVAT += line.Amount * (int)line.VatPercent.Value / 100;
}
totalInclVAT = totalExclVAT + totalVAT;
// Adding keys if necessary
if (!ei.OriginalValuesMap.ContainsKey("TotalExclVAT"))
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalInclVAT"))
ei.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalVAT"))
ei.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
// Modifying total values
invoice.TotalExclVAT = totalExclVAT;
invoice.TotalInclVAT = totalInclVAT;
invoice.TotalVAT = totalVAT;
return saveMap;
}
The solution above works well whenever the invoice & the invoiceLines are modified client side. I have a problem when no invoice is modified client side (only lines modified). In this case I need to add the related invoice to the saveMap by getting it from DB. That's what I do in my code as you can see. But I need to add keys to the OriginalValuesMap for properties I manually modified here and I cannot in this case because my dictionary object is null. Then when I do...
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
... on a null object (OriginalValuesMap) it doesn't work.
So my new problem is now the next: how to add an entity to the saveMap which already exists on DB. So I don't want to mark this entity as ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Add); but rather ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);. In this case my OriginalValuesMap is null and it seems to be a problem.
Hope you understand what I try to explain here.
Is there any reason not to use a triggered stored procedure for this? This would certainly be the simplest approach...
But... if there is, then the other approach would be to use 'BeforeSaveEntities' instead of 'BeforeSaveEntity' because this will give you access to the entire 'SaveMap'.
Then create a hashset of all of the invoiceLines for each modified invoice and construct this as the combination of your server side query of invoice lines per invoice overlayed with client side invoiceLines associated with this invoice (from the SaveMap). Next just total each hashSet and use this update your 'Totalxxx' properties.
A little terse but hopefully this makes sense.

How to improve this code of query in mongodb

This is what my employee use
var client = new MongoClient(System.Configuration.ConfigurationManager.AppSettings["MongoDbServer"]);
var server = client.GetServer();
MongoDatabase db = server.GetDatabase("Edbert");
var collection = db.GetCollection("testInstagram");
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
I think this part is ugly:
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
What he tries to do is to set the query to return true if size of PossibleInstagramIDs are bigger than 1.
What should he have done?
I would make the collection strongly typed by creating a TestInstagram class and then use LINQ.
Your TestInstagram class will look something like this:
public class TestInstagram
{
public ObjectId Id { get; set; }
public IEnumerable<int> PossibleInstagramIDs { get; set; }
public int InstagramID { get; set; }
}
And this will allow you to simply query as follows:
var collection = _mongoDatabase.GetCollection<TestInstagram>("testInstagram");
collection.AsQueryable().Where(ti => ti.PossibleInstagramIDs.Count() > 1 && ti.InstagramID == null);

Converting ESQL to LINQ to Entities. Sort by related entities

I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}

ASP NET MVC How to call Count on an attribute then print out a sorted list?

Say I had a class:
public class Post
{
public int PostId { get; set; }
public string Topic { get; set; }
public int UserId { get; set; }
[StringLength(5000)]
public string Body { get; set; }
public DateTime DateCreated { get; set; }
public string Name { get; set; }
public int Votes { get; set; }
}
And for each post, a user could input a topic. for example, if the topics were "Red" "Green" "Blue" and "Yellow", how could I create a list based on how many times those were used?
An example output:
Red | 70
Blue | 60
Green | 40
Yellow| 35
EDIT: How come this doesn't work and gives me an error where I cannot implicitly convert the type?
public List<string> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
EDIT 2:
So I tried your solution out Dustin, and I'm getting an error. This is what I used:
public IEnumerable<IGrouping<string,int>> GetPosts()
{
var posts = from p in db.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
return posts.ToList();
}
This is giving me an error under posts.ToList():
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable>'. An explicit conversion exists (are you missing a cast?)
To create the grouping you create an anonymous type such as:
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
Then to work with the date, lets say iterate over it:
foreach(var p in posts)
{
Response.Write(String.Format("{0} - {1}", p.Topic, p.Count));
}
You must create a new type if you do a projection and return it form method!
public class MyCounts
{
public string Topic { get; set; }
public int Count { get; set; }
}
public List<MyCounts> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new MyCounts
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
The problem is that you need to use an non anonymous type for your return value.
This query creates an IEnumerable of anonymous types.
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
It's the select new statement that creates the anonymous objects.
What you need to do is to create something that is non anonymous - an object that can be shared within and outside this method.
Like this:
public IEnumerable<TopicAndCount> GetPosts()
{
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new TopicAndCount
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
}
Note the select new TopicAndCount statement and the return value of the enclosing method.
That will solve your problem.