RavenDB: How can I properly index a cartesian product in a map-reduce? - ravendb

This question is a spin-off of RavenDB: Why do I get null-values for fields in this multi-map/reduce index?, but I realized, the problem was another.
Consider my extremely simplified domain, rewritten to a movie rental store scenario for abstraction:
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
It's a text-book many-to-many example.
The index I want to create is this:
For a given user, give me a list of every movie in the database (filtering/search left out for the moment) along with an integer describing how many times (or zero) the user has rented this movie.
Basically like this:
Users:
| Id |
|--------|
| John |
| Lizzie |
| Albert |
Movies:
| Id |
|--------------|
| Robocop |
| Notting Hill |
| Inception |
MovieRentals:
| Id | UserId | MovieId |
|-----------|--------|--------------|
| rental-00 | John | Robocop |
| rental-01 | John | Notting Hill |
| rental-02 | John | Notting Hill |
| rental-03 | Lizzie | Robocop |
| rental-04 | Lizzie | Robocop |
| rental-05 | Lizzie | Inception |
Ideally, I want an index to query, that would look like this:
| UserId | MovieId | RentalCount |
|--------|--------------|-------------|
| John | Robocop | 1 |
| John | Notting Hill | 2 |
| John | Inception | 0 |
| Lizzie | Robocop | 2 |
| Lizzie | Notting Hill | 0 |
| Lizzie | Inception | 1 |
| Albert | Robocop | 0 |
| Albert | Notting Hill | 0 |
| Albert | Inception | 0 |
Or declaratively:
I always want a full list of all the movies (eventually I will add filtering/searching) - even when providing a user that has never rented a single movie yet
I want a count of the rentals for each user, just the integer
I want to be able to sort by the rental-count - i.e. show the most-rented movies for a given user at the top of the list
However, I can't find a way to make the "cross-join" above and save it in the index. Instead, I initially thought I got it right with this maneuver below, but it does not allow me to sort (see failing test):
{"Not supported computation: x.UserRentalCounts.SingleOrDefault(rentalCount => (rentalCount.UserId == value(UnitTestProject2.MovieRentalTests+<>c__DisplayClass0_0).user_john.Id)).Count. You cannot use computation in RavenDB queries (only simple member expressions are allowed)."}
My question is basically: how can I - or can I at all - index so, that my requirements are fulfilled?
Below is my mentioned example, that does not fulfill my requirements, but that's where I am right now. It uses the following packages (VS2015):
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net461" />
<package id="NUnit" version="3.5.0" targetFramework="net461" />
<package id="RavenDB.Client" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Database" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Tests.Helpers" version="3.5.2" targetFramework="net461" />
</packages>
MovieRentalTests.cs
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using Raven.Tests.Helpers;
namespace UnitTestProject2
{
[TestFixture]
public class MovieRentalTests : RavenTestBase
{
[Test]
public void DoSomeTests()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
//Test-data
var user_john = new User { Id = "John" };
var user_lizzie = new User { Id = "Lizzie" };
var user_albert = new User { Id = "Albert" };
var movie_robocop = new Movie { Id = "Robocop" };
var movie_nottingHill = new Movie { Id = "Notting Hill" };
var movie_inception = new Movie { Id = "Inception" };
var rentals = new List<MovieRental>
{
new MovieRental {Id = "rental-00", UserId = user_john.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-01", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-02", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-03", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-04", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-05", UserId = user_lizzie.Id, MovieId = movie_inception.Id}
};
//Init index
new Movies_WithRentalsByUsersCount().Execute(store);
//Insert test-data in db
using (var session = store.OpenSession())
{
session.Store(user_john);
session.Store(user_lizzie);
session.Store(user_albert);
session.Store(movie_robocop);
session.Store(movie_nottingHill);
session.Store(movie_inception);
foreach (var rental in rentals)
{
session.Store(rental);
}
session.SaveChanges();
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
}
//Test of correct rental-counts for users
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.ToList();
var robocopWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_robocop.Id);
Assert.AreEqual(1, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(2, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
var nottingHillWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_nottingHill.Id);
Assert.AreEqual(2, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
}
// Test that you for a given user can sort the movies by view-count
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.OrderByDescending(x => x.UserRentalCounts.SingleOrDefault(rentalCount => rentalCount.UserId == user_john.Id).Count)
.ToList();
Assert.AreEqual(movie_nottingHill.Id, allMoviesWithRentalCounts[0].MovieId);
Assert.AreEqual(movie_robocop.Id, allMoviesWithRentalCounts[1].MovieId);
Assert.AreEqual(movie_inception.Id, allMoviesWithRentalCounts[2].MovieId);
}
}
}
public class Movies_WithRentalsByUsersCount :
AbstractMultiMapIndexCreationTask<Movies_WithRentalsByUsersCount.ReducedResult>
{
public Movies_WithRentalsByUsersCount()
{
AddMap<MovieRental>(rentals =>
from r in rentals
select new ReducedResult
{
MovieId = r.MovieId,
UserRentalCounts = new[] { new UserRentalCount { UserId = r.UserId, Count = 1 } }
});
AddMap<Movie>(movies =>
from m in movies
select new ReducedResult
{
MovieId = m.Id,
UserRentalCounts = new[] { new UserRentalCount { UserId = null, Count = 0 } }
});
Reduce = results =>
from result in results
group result by result.MovieId
into g
select new
{
MovieId = g.Key,
UserRentalCounts = (
from userRentalCount in g.SelectMany(x => x.UserRentalCounts)
group userRentalCount by userRentalCount.UserId
into subGroup
select new UserRentalCount { UserId = subGroup.Key, Count = subGroup.Sum(b => b.Count) })
.ToArray()
};
}
public class ReducedResult
{
public string MovieId { get; set; }
public UserRentalCount[] UserRentalCounts { get; set; }
}
public class UserRentalCount
{
public string UserId { get; set; }
public int Count { get; set; }
}
}
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
}
}

Since your requirement says "for a given user", if you really are looking only for a single user, you can do this with a Multi-Map index. Use the Movies table itself to produce the baseline zero-count records and then map in the actual MovieRentals records for the user on top of that.
If you really need it for all users crossed with all movies, I don't believe there is a way to do this cleanly with RavenDB as this would be considered reporting which is noted as one of the sour spots for RavenDB.
Here are some options if you really want to try to do this with RavenDB:
1) Create dummy records in the DB for every user and every movie and use those in your index with a 0 count. Whenever a movie or user is added/updated/deleted, update the dummy records accordingly.
2) Generate the zero-count records yourself in memory on request and merge that data with the data that RavenDB gives you back for the non-zero counts. Query for all users, query for all movies, create the baseline zero-count records, then do the actual query for non-zero counts and layer that on top. Finally, apply paging/filtering/sorting logic.
3) Use the SQL replication bundle to replicate the Users, Movies, and MovieRental tables out to SQL and use SQL for this "reporting" query.

Related

Retrieve values from a table without using filters

I have two tables
Estatus table
+----+----------+
| ID | Nombre |
+----+----------+
| 16 | ACTIVE |
| 2 | DISABLED |
+----+----------+
TipoCarga Table
+----+----------+--------------------+-----------+
| ID | Nombre | Descripcion | EstatusID |
+----+----------+--------------------+-----------+
| 1 | Active | first descriptio | 16 |
| 2 | Disabled | second description | 2 |
+----+----------+--------------------+-----------+
and I have custom entity to list and charge like:
IEnumerable<T> Listar(Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "");
T Cargar(Expression<Func<T, bool>> filter,
string includeProperties = "");
if I do something like
var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
var estatusid = _estatusService.Cargar(e => e.Nombre.Equals("ACTIVE") && e.ClaseID.Equals(claseid)).ID;
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
It load values, but only values who have Nombre.Equals("ACTIVO"), and I want to get all Nombre values
So first I try to get a list of all Estatus like:
//I don´t need this line because I dont wannt to filte by clase
//var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
//get list of all estatus
var estatusid = _estatusService.Listar();
then use this list and use to retrieve value "Nombre" depending of EstatusID of table TipoCarga like:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
Problem is var estatusid = _estatusService.Listar(); get 0 values, and I don´t know why it don´t get values, can someone help ? Regards
Methods implementations:
public virtual IEnumerable<T> Listar(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).AsEnumerable<T>();
}
else
{
return query.AsEnumerable<T>();
}
}
public virtual T Cargar(
Expression<Func<T, bool>> filter,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
query = query.AsNoTracking().Where(filter);
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
return query.FirstOrDefault();
}
UPDATE:
If I use .Tolist as Ivan Stoev comment it return all values of Estatus but now it pass to:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
then it load foreach like:
if (tc != null)
{
List<TipoCargaViewModel> tiposcargas = new List<TipoCargaViewModel>();
foreach (var item in tc)
{
var carga = new TipoCargaViewModel()
{
ID = item.ID,
Nombre = item.Nombre,
Descripcion = item.Descripcion,
EstatusID = item.EstatusID,
NombreEstatus = item.Estatus.Nombre
};
tiposcargas.Add(carga);
}
But when it try to pass foreach it throw an exception:
Unable to create a constant value of type 'Project.Model.Estatus'.
Only primitive types or enumeration types are supported in this
context.

How to get data with #ManyToMany annotated tables?

My related tables are below.
Person Table
id
name
surname
Location Table
id
title
point
Person_Location Table
person_id
location_id
I want to get person and location values like this..
id | name | surname | title | point
1 john | adas | my home | 44,45
1 | John | adas | brother's home | 55,33
How can I get the users and their locations in hibernate?
try this out :
you can get an array of object and you can manipulat them as you want
String stringQuery="select p.id,p.name,p.surname,l.title,l.point from person p, location l,person_location pl where "
+ "p.id=pl.person_id and l.id=pl.location_id";
Query query=entityManager.createNativeQuery(stringQuery);
List<Object[]> result=query.getResultList();
later you can get the person Id by result.get(i)[0]
or you can create a custom class which will not be a managed entity:
public class customPerson{
id | name | surname | title | point
private int id;
private String name;
private String surname;
private String title;
private String doube point;
//getters&setters
//constructors (required) one default ant one with all your attributes
public CustomPerson(){}
public customPerson(int id,...){
...
}
}
later in your Dao you can get the result you want through the custom object:
String stringQuery="select p.id,p.name,p.surname,l.title,l.point from person p, location l,person_location pl where "
+ "p.id=pl.person_id and l.id=pl.location_id";
Query query=entityManager.createNativeQuery(stringQuery,CustomPerson.class);
List<CustomPerson> result=query.getResultList();
// In Person class:
#ManyToMany
#JoinTable(name="Person_Location",
joinColumns=
#JoinColumn(name="person_id", referencedColumnName="ID"),
inverseJoinColumns=
#JoinColumn(name="location_id", referencedColumnName="ID")
)
public Set<Locations> getLocations() { return locations; }
// In Location:
#ManyToMany(mappedBy="locations")
public Set<Person> getPersons() { return persons; }
In Person class
#OneToMany(mappedBy="person")
public Set<personLocations> getPersonLocation { return personLocations; }
In Location class
#OneToMany(mappedBy="location")
public Set<personLocations> getPersonLocation { return personLocations; }
In personLocations
#ManyToOne
#JoinColumn(name="person_ID")
public Person getPerson() { return person; }
#ManyToOne
#JoinColumn(name="location_ID")
public Location getlocation() { return location; }

how to compare string linq with sub query with another linq with sub query

I Have 4 tables:
Position Table:
| Position | PositionId |
| driver | 1 |
| clerk | 2 |
position Skill table:
| SkillId | skill | PositionId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
Worker table:
| Name | WorkerId |
| John | 1 |
| alex | 2 |
Worker skill table:
| skillId | skill | WorkerId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
I join the position table with position Skill table
and worker table with worker skill
What I'm having trouble with is how can I compare the two joined tables to have a result of
for example:
I need to know who's worker have all the specific skills that the position have
Like:
I Select position with positionId of 1 and have the skillname of driving and drifting
I need to get the Worker with the same skills with driving and drifting also
so far i got this:
var PositionsWithSkills = (from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
}).ToList();
var WorkersWithSkills = (from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills
select new
{
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills
where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill))
select worker.workerId)
}).ToList();
the two query works well.. but the last query where i must compare the two query =.. i cant get the worker id
and can i turn this to a stored proc?
Sorry if am wrong. What I got to know from your question is you want the workers list satisfying all the skills of the position you pass. If this is what you want you may try this:
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId
where ((from y in db.workerSkillList where y.WorkerId == u.WorkerId select y).Count() == (from p in db.positionSkillList where p.PositionId == 1("pass your positionId here") select p).Count())
select u).ToList().Distinct();
or if you want to use lambda expression you can use this
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y=> y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p=>p.PositionId == 1).Count()) select u).ToList().Distinct();
For more understanding you can try the below code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
IList<Position> positionList = new List<Position>() {
new Position(){ position="Driver", PositionId=1}
,new Position(){ position="clerk", PositionId=2}
};
IList<PositionSkill> positionSkillList = new List<PositionSkill>() {
new PositionSkill(){ Skill = "driving",skillid = 1,PositionId = 1}
,new PositionSkill(){ Skill = "drifting",skillid = 2,PositionId = 1}
};
IList<Worker> workerList = new List<Worker>() {
new Worker(){ name = "John",WorkerId = 1}
,new Worker(){ name = "alex",WorkerId = 2}
};
IList<WorkerSkill> workerSkillList = new List<WorkerSkill>(){
new WorkerSkill(){Skill = "driving",skillid = 1,WorkerId = 2}
, new WorkerSkill(){Skill = "drifting",skillid = 2,WorkerId = 2}
};
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y => y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
foreach (var worker in workerWithallSkill)
{
Console.WriteLine(worker.name);
}
Console.ReadLine();
}
}
public class Position
{
public string position { get; set; }
public int PositionId { get; set; }
}
public class PositionSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public string name { get; set; }
public int WorkerId { get; set; }
}
public class WorkerSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}
}
if a worker has skills from different positions the above code will not work, if this is the scenario try the below code:
var WorkerPositionSkill = from p in db.positionSkillList join q in db.workerSkillList on p.skillid equals q.skillid select new { posSkill = p, workerSkill = q };
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId where (WorkerPositionSkill.Where(y => y.workerSkill.WorkerId == u.WorkerId && y.posSkill.PositionId == 1).Count() == db.positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
This is highly unlikely to work with Linq To SQL because...its a huge steaming pile of #$%&. But this Linq query should given a sufficiently magical IQueryProvider give the right SQL. I've seen some very magical things come from Entity Framework.
var PositionsWithSkills = from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
};
var WorkersWithSkills = from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
};
var PositionWithSkilledWorkers = from pos in PositionsWithSkills
from worker in WorkersWithSkills
where pos.RequiredSkill.All(worker.Skills.Contains)
group worker.Name by pos.PositionID;
PS please learn to use associations as opposed to join/where. If you are going to use join/where, you might as well just use SQL.
var PositionsWithSkills = (from a in Positions select new {
PositionID = a.PositionId,
RequiredSkills = (from b in PositionSkills where b.PositionId == a.PositionId select b.skillId).ToList()
}).ToList();
var WorkersWithSkills = (from x in Workers select new {
Name = x.Name,
Skills = (from y in WorkerSkills where y.WorkerId == x.WorkerID select y.skillId).ToList()
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills select new {
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill)) select worker.Name).ToList()
}).ToList();
i think, your database's tables have not been designed correctly...
you need a relation between worker skill and position skill, i think your tables must be desinged like this:
Skill table: SkillID, Skill
Position table: PositionID, Position
PositionSkill table: ID, SkillID, PositionID
Worker table: WorkerID, Name
WorkerSkill table: ID, SkillID, WorkerID
but by this way you designed your tables, if assume skill field (description of skills) are the same in worker skill and position skill, we can use this as a relation, and your query can be like this:
// skills of specific position
var PositionSkills = Context.PositionSkill.Where(u => u.PositionId == 1);
var WorkersWithSkills = Context.Worker
.Join(Context.WorkerSkill,
worker => worker.WorkerId,
workerSkill => workerSkill.WorkerId,
(worker, workerSkill) => new { worker, workerSkill })
.GroupBy(u => u.worker)
.Select(u => new
{
u.Key.WorkerId,
u.Key.Name,
Skills = u.Select(t => t.workerSkill.skill)
});
var SkilledWorkers = WorkersWithSkills
.Where(u => PositionSkills.All(t => u.Skills.Contains(t.skill)))
.ToList();
if you wont change your database's tables, you can add a join table between position skill and worker skill like:
WorkerPositionSkill: PositionSkillID, WorkerSkillID
Here is a LinqPad program that returns the result, { Worker = John, Position = Driver}. If I understand your requirements you want to find a worker who satisfies the conditions where the worker has all the skills required for Position = 1, which is driving and drifting skills. The query returns two rows the following [{worker = John, Position = Driver}, {worker = John, Position = Driver}]. I had to use distinct to display it once. The reason for two rows is he satisfies both driving and drifting job skills. If the position required 4 skills in which the worker met, there would be 4 duplicate rows. The unique fixes that problem. Hope this helps you along.
I created this solution in LinqPad, which is great tool with hundred's of very well documented linq query examples.
void Main()
{
// Table Setup
// ************
var position = new List<Position>();
position.Add(new Position { Id = 1, Name = "driver" });
position.Add(new Position { Id = 2, Name = "clerk" });
var positionSkill = new List<PositionSkill>();
positionSkill.Add(new PositionSkill { Id = 1, Skill = "driving", PositionId = 1 });
positionSkill.Add(new PositionSkill { Id = 2, Skill = "drifting", PositionId = 1 });
var worker = new List<Worker>();
worker.Add(new Worker { Id = 1, Name = "John" });
worker.Add(new Worker { Id = 2, Name = "alex" });
var workerSkill = new List<WorkerSkill>();
workerSkill.Add(new WorkerSkill { Id = 1, Skill = "driving", WorkerId = 1 });
workerSkill.Add(new WorkerSkill { Id = 2, Skill = "drifting", WorkerId = 1 });
// The Query
// *********
var positionValue = 1;
var r = from p in position
join ps in positionSkill on p.Id equals ps.PositionId
join ws in workerSkill on ps.Skill equals ws.Skill
join w in worker on ws.WorkerId equals w.Id
where p.Id == positionValue
select new {
PositionName = p.Name,
WorkerName = w.Name
};
// Get Distinct Names
r.Distinct().Dump();
}
// Define other methods and classes here
public class Position
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PositionSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkerSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}

Return properties from subclass left joins when querying superclass in NHibernate

I have an ICriteria that returns properties from a superclass Animal. Now I want to include in the results several properties from a subclass, Bird. For other subclasses, these properties should return null. I am using table-per-subclass inheritance. Is there any way to do this without adding lots of DetachedCriteria? NHibernate already left joins on the subclass tables; is there a way to project values from those joins?
Update: I need to be able to sort and filter on the subclass properties, and the entire query needs to support paging.
Here is my model:
public abstract class Animal
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Cat : Animal
{
public int WhiskerCount { get; set; }
}
public class Bird : Animal
{
public long WingSpan { get; set; }
}
Given the following tables:
Animal:
Id | Name
----+--------------
1 | Sylvester
2 | Tweety
3 | Maru
4 | Big Bird
Cat:
Id | WhiskerCount
----+--------------
1 | 6
3 | 12
Bird:
Id | Wingspan
----+--------------
2 | 0.5
4 | 10
I want the following result set:
Id | Name | Wingspan
----+------------+-------------
1 | Sylvester | <null>
2 | Tweety | 0.5
3 | Maru | <null>
4 | Big Bird | 10
var results = session.CreateCriteria<Animal>()
.List<Animal>()
.Select(a => new
{
Id = a.Id,
Name = a.Name,
Wingspan = (a is Bird) ? ((Bird)a).Wingspan : (int)null
});

Get the data 'less common as possible' from a collection

Starting from a table like this:
| Code | Year |
---------------
| A01 | 2001 |
| A01 | 2002 |
| B01 | 2002 |
| C01 | 2003 |
I have to arrive to this:
| Code | Year |
---------------
| A01 | 2001 |
| B01 | 2002 |
| C01 | 2003 |
I have to group the first column (Code) and from the second (Year) I have to get the data 'less common as possible' compared to all the other records. I try to explain this with the example: for the code 'A01' I have 2 years: '2001' and '2002'. I have to take '2001' because it's the one that not recurs in the other records. In the case that there aren't available values 'Year' that not recurs in the other records, it's good to take whatever value.
The data are in the form of array in memory and to interact with them I'm useng some LINQ queries.
Thank you in advance!
Pileggi
I'm sorry for making it in C#. Hope you will not have problems to convert it to VB.NET.
var filteredItems = items
.Select(cod => cod.Code).Distinct()
.Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod))
.ToList();
Test code:
public class Item
{
public string Code { get; set; }
public string Year { get; set; }
}
public static void Main(string[] args)
{
var items =
new List<Item>
{
new Item{ Code = "A01", Year = "2001" },
new Item{ Code = "A01", Year = "2002" },
new Item{ Code = "B01", Year = "2002" },
new Item{ Code = "C01", Year = "2003" },
};
var filteredItems = items
.Select(cod => cod.Code).Distinct()
.Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod))
.ToList();
}
Here is the right answer (to be compared with Alex Aza's one : filteredItemsAlexAza and filteredItemsSsithra give different results since the less common data is not also the minimum one anymore - here 2005 instead of 2001 for A01)
class Program
{
public static void Main(string[] args)
{
var items = new List<Item>
{
new Item { Code = "A01", Year = "2005" },
new Item { Code = "A01", Year = "2002" },
new Item { Code = "B01", Year = "2002" },
new Item { Code = "C01", Year = "2003" },
};
var filteredItemsAlexAza = items.Select(cod => cod.Code).Distinct().Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod)).ToList();
var filteredItemsSsithra = items
.Select(item => new { Item = item, NbItemsWithSameYear = items.Where(i => i.Year == item.Year).Count() })
.GroupBy(ano => ano.Item.Code)
.Select(group => group.OrderBy(ano => ano.NbItemsWithSameYear).First().Item)
.ToList();
}
public class Item
{
public string Code { get; set; }
public string Year { get; set; }
}
}
You can google for "Concordance source code" in your preferred implementation language.
Sorry but the proposed solution absolutely doesn't fullfill Pileggi's initial requirements.
"The less common value as possible" has become "the min value".
Both match in this precise case, which gives the illusion that Alex Aza's answer is right but it is just a coincidence.