RavenDb: querying by a property in related entity - ravendb

Either I'm having a mental block, or its not that straightforward.
I have 2 classes, something like that:
public class House
{
public string Id { get; set; }
public string City { get; set; }
public string HouseNumber { get; set; }
}
public class Person
{
public string Id { get; set; }
public string HouseId { get; set; }
public string Name { get; set; }
}
Now I want a list of all people living in a given city, in a flattened model ({City, HouseNumber, PersonName}).
I can't figure out a way on how to map that.. If I had a City in a Person class that would be easy, but I don't, and it doesn't make sense there, imo.
Help ?
Edit:
I came up with this index, which actually works with in-memory list, but Raven returns nothing :(
public class PeopleLocations : AbstractMultiMapIndexCreationTask<PeopleLocations.EntryLocation>
{
public class PeopleLocation
{
public string PersonId { get; set; }
public string HouseId { get; set; }
public string City { get; set; }
}
public PeopleLocations()
{
this.AddMap<House>(venues => venues.Select(x => new
{
x.City,
HouseId = x.Id,
PersonId = (string)null
}));
this.AddMap<Person>(people => people.Select(x => new
{
City = (string)null,
HouseId = x.HouseId,
PersonId = x.Id
}));
this.Reduce = results => results.GroupBy(x => x.HouseId)
.Select(x => new
{
HouseId = x.Key,
People = x.Select(e => e.PersonId),
City = x.FirstOrDefault(y => y.City != null).City,
})
.SelectMany(x =>
x.People.Select(person => new PeopleLocation
{
PersonId = person,
HouseId = x.HouseId,
City = x.City,
})
)
.Select(x => new { PersonId = x.PersonId, x.City, x.HouseId });
}
}

You can do this with a MultiMap Index - but there's a great new feature in RavenDB 2.0 called Indexing Related Documents that is much easier.
Map = people => from person in people
let house = LoadDocument<House>(person.HouseId)
select new
{
house.City,
house.HouseNumber,
PersonName = person.Name,
}

Related

Linq Select statement inside Order By

Is it possible to write an linq select inside order by something like :
ReservationDTO obj = new ReservationDTO();
obj.bookroomview = obj.bookroomview.GroupBy(a => a.RoomFloor)
.Select(a => obj.bookroomview
.Select(x => new { Amount = a.Select(b => b.ReservationRoomID).Count(), Name = a.Key })
.OrderBy(x => x.Amount)
).ToList().AsQueryable();
My aim is to select all obj.roombookview items but sort them according to Count calculation. If it is possible how can i write it?
My view class :
public partial class Book_Room_View
{
public Nullable<int> ReservationID { get; set; }
public Nullable<System.DateTime> StartDate { get; set; }
public Nullable<System.DateTime> EndDate { get; set; }
public Nullable<int> ReservationRoomID { get; set; }
public string RoomName { get; set; }
}
if you were to order the groups by reservations, I guess this is the proper way doing it, you don't need to count specific attribute as I guess you are trying, just order by the count of the value list.
obj.bookroomview.GroupBy(a => a.RoomFloor)
.OrderBy(gr => gr.Count())
.Select(gr => new { Amount = gr.Count(), Name = gr.Key })
.AsQueryable();

RavenDB - How to merge related docs of same type into an index projection

Assuming the following class:
public class Thing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
}
and the following result class:
public class ProjectedThing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
public IEnumerable<Thing> Things { get; set; }
}
How can I build an index that holds the result class?
The closest I've come is with the following index definition:
public class ProjectedThings : AbstractIndexCreationTask<Thing,ProjectedThing>
{
public ProjectedThings()
{
Map = docs => from doc in docs
select new
{
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
Things = new[] {
new Thing{
Id = doc.Id,
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
TitlePluralized = doc.TitlePluralized
}
}
};
Reduce = results => from r in results
group r by r.KeyId into g
select new
{
Title = g.FirstOrDefault(x => x.Id == x.KeyId).Title,
KeyId = g.Key,
Culture = g.FirstOrDefault(x => x.Id == x.KeyId).Culture,
Things = from thing in g.SelectMany(x => x.Things).Where(x => x.Id != x.KeyId)
select new
{
Id = thing.Id,
Title = thing.Title,
KeyId = thing.Key,
Culture = thing.Culture
}
};
}
}
That's almost doing the trick, but I can't collect the Title, KeyId, and Culture in the reduction. Only the Things property is being populated.
Look at your code:
g.FirstOrDefault(x => x.Id == x.KeyId)
I don't understand this, but this is likely to be always false.
You probably want:
g.FirstOrDefault().Title,

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.

Trim string NHIBERNATE

Can't believe I've found no answer to this but how can you do a query like
SELECT LTRIM(RTRIM("ColumnName")) FROM ....
in NHibernate
thanks
Having an example of Bank as POCO:
public class Bank
{
public virtual int ID { get; set; }
public virtual string City { get; set; }
public virtual string Street { get; set; }
}
There is a syntax for the LTRIM(RTRIM...
Bank bank = null;
var session = ...;
var query = session.QueryOver<BankAddress>()
.SelectList(l => l
// properties ID, City
.Select(c => c.ID).WithAlias(() => bank.ID)
.Select(c => c.City).WithAlias(() => bank.City)
// projection Street
.Select(Projections.SqlProjection(
" LTRIM(RTRIM({alias}.Street)) as Street" // applying LTRIM(RTRIM
, new string[] { "Street" }
, new IType[] { NHibernate.NHibernateUtil.String }
))
.TransformUsing(Transformers.AliasToBean<Bank>())
;
var list = query.List<Bank>();

How do I retrieve multiple Count fields into a single User Report?

Say I have a User class like this:
public class User
{
public string Id {get; set;}
public string Name {get; set;}
}
Each User can be either a Mentor, a Mentee or both. This is represented by a Relationship class:
public class Relationship
{
public string MentorId {get; set;} // This is a User.Id
public string MenteeId {get; set;} // This is another User.Id
}
Now I would like to generate a report that lists all of my Users and contains a field called Mentor Count and another field called Mentee Count. To achieve this I have created a UserReportDTO class to hold my report data.
public class UserReportDTO
{
public string Name {get; set;}
public string MentorCount {get; set;}
public string MenteeCount {get; set;}
}
I then query my RavenDB to get a list of all the Users and transform this into a list of UserReportDTO instances.
UserService
public List<UserReportDTO> GetReportChunk(
IDocumentSession db,
int skip = 0,
int take = 1024)
{
return db.Query<User>()
.OrderBy(u => u.Id)
.Skip(skip)
.Take(take)
.ToList()
.Select(user =>
new UserReportDTO
{
Name = user.Name,
MentorCount = // What goes here?
MenteeCount = // What goes here?
})
.ToList();
}
As you can see, I am struggling to work out the best way to retrieve the MentorCount and MenteeCount values. I have written some Map/Reduce Indexes that I think should be doing the job but I am unsure how to use them to achieve the result I want.
Question
What is the best way to include multiple aggregate fields into a single query?
EDIT 1
#Matt Johnson: I have implemented your index (see end) and now have a working Report Query which, in case anybody is interested, looks like this:
Working User Report Query
public List<UserDTO> GetReportChunk(IDocumentSession db, Claim claim, int skip = 0, int take = 1024)
{
var results = new List<UserDTO>();
db.Query<RavenIndexes.Users_WithRelationships.Result, RavenIndexes.Users_WithRelationships>()
.Include(o => o.UserId)
.Where(x => x.Claims.Any(c => c == claim.ToString()))
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.ToList()
.ForEach(p =>
{
var user = db.Load<User>(p.UserId);
results.Add(new UserDTO
{
UserName = user.UserName,
Email = user.Email,
// Lots of other User properties
MentorCount = p.MentorCount.ToString(),
MenteeCount = p.MenteeCount.ToString()
});
});
return results;
}
MultiMap Index
public class Users_WithRelationships :
AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string[] Claims { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => users.Select(user => new
{
UserId = user.Id,
user.Claims,
MentorCount = 0,
MenteeCount = 0
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MentorId,
Claims = (string[]) null,
MentorCount = 0,
MenteeCount = 1
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MenteeId,
Claims = (string[]) null,
MentorCount = 1,
MenteeCount = 0
}));
Reduce = results => results.GroupBy(result => result.UserId).Select(g => new
{
UserId = g.Key,
Claims = g.Select(x => x.Claims).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
});
}
}
You might be better served with a model that already has your relationship data kept with the user. This might look something like:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string[] MentorUserIds { get; set; }
public string[] MenteeUserIds { get; set; }
}
However, if you want to stick with the model you described, the solution is to get rid of the multiple separate indexes and create a single multi-map index that has the data you need.
public class Users_WithRelationships
: AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string Name { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => from user in users
select new
{
UserId = user.Id,
Name = user.Name,
MentorCount = 0,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MentorId,
Name = (string)null,
MentorCount = 1,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MenteeId,
Name = (string)null,
MentorCount = 0,
MenteeCount = 1
});
Reduce = results =>
from result in results
group result by result.UserId
into g
select new
{
UserId = g.Key,
Name = g.Select(x => x.Name).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
};
}
}
Then you can update your GetReportChunk method to query against the one index if you still want to project a custom DTO.
return db.Query<Users_WithRelationships.Result, Users_WithRelationships>()
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.Select(x =>
new UserReportDTO
{
Name = x.Name,
MentorCount = x.MentorCount,
MenteeCount = x.MenteeCount,
})
.ToList();