Index with boost - how to do a BeginsWith query - ravendb

I have a document entity Student
public class Student
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
I have an index Student_ByName with a boost on the FirstName property defined as
public class Student_ByName : AbstractIndexCreationTask<Domain.Student>
{
public Student_ByName()
{
Map = students => from s in students
select new
{
FirstName = s.FirstName.Boost(6),
s.LastName,
s.DateOfBirth,
s.Gender
};
}
}
I have the following Student document instances
{ FirstName: 'David', LastName: 'Globe', DateOfBirth: '02/04/2000' }
{ FirstName: 'Tyson', LastName: 'David', DateOfBirth: '23/10/2000' }
{ FirstName: 'David', LastName: 'James', DateOfBirth: '19/05/1996' }
then the query below does not promote rows where David is the first name to the top of the list.
var students = _session.Query<Domain.Student, Student_ByName>()
.Where(s => s.FirstName.StartsWith('David') ||
s.LastName.StartsWith('David'))
.ToList();
If I change the Where clause to check for equality then the rows with David as the first name are promoted to the top of the list
.Where(s => s.FirstName == 'David' || s.LastName == 'David')
.ToList();
My question is how can I get the boost on the FirstName to work when doing a BeginsWith search.

This is expected, see the explanation here:
http://grokbase.com/t/lucene/java-user/022dzkexc6/prefixquery-scoring
You can do this with query time (vs index time) scoring, using:
var students = session.Advanced.LuceneQuery<Student>()
.WhereStartsWith("FirstName", "David").Boost(3)
.WhereStartsWith("LastName", "David")
.ToList();

Related

IQueryable not generating the desired query in entity framework core

I have some entities similar to the following:
public class Teacher
{
public int TeacherId { get; set; }
public string TeacherName { get; set; }
public int Age { get; set; }
public string CurrentGrade { get; set; }
public int YearsTeaching { get; set; }
pubilc ICollection<StudentFeedback> StudentFeedback { get; set; }
}
public class StudentFeedback
{
public int StudentFeedBackId { get; set; }
public int TeacherId { get; set; }
public int StudentId { get; set; }
public string Feedback { get; set; }
public Teacher Teacher { get; set; }
public Student Student { get; set; }
}
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
public string CurrentGrade { get; set; }
}
I have a repository with a method where I want to return a teacher or list of teachers where the StudentFeedback returned belongs to the student who is looking at it (the studentId is stored in a token).
So, lets say I have the Teacher (teacherId) and a Student (userId) who is hitting the API endpoint. I currently have the following:
int teacherId = 2;
int userId = 20; // This is the currently logged in user, extracted from the token.
var query = _context.Teachers.AsQueryable();/* _context is the DataContext*/
query = query.Where(t => p.TeacherId == teacherId);
query = query.Where(u => u.StudentFeedback.Any(x => x.StudentId == userId));
However this is still returning all StudentFeedback from all Students, so long as the userId (student) has feedback provided for the teacher in question. I had a look at the query that gets executed and the problem is that the studentId predicate is in the wrong place. A very rough version of the query is:
SELECT *
FROM ( SELECT t.*
FROM dbo.Teachers t
WHERE (t.TeacherId = 2)
AND EXISTS ( SELECT 1
FROM dbo.StudentFeedback t0
WHERE (t.TeacherId = t0.TeacherId)
AND (t0.StudentId = 20))) p
LEFT JOIN dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId
Whereas it should be something like
SELECT *
FROM ( SELECT t.*
FROM dbo.Teachers t
WHERE (t.TeacherId = 2)) p
LEFT JOIN dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId
AND sf.StudentId = 20
but I don't know how to make that happen. Is there something wrong with the IQueryable predicates I've setup or have I missed some logic in the modelBuilder within the datacontext?
Thank-you.
Edit: I am using Entity Framework Core 5.0.2 and I am also using Automapper with the following code:
query.ProjectTo<TeacherDTO>(_mapper.ConfigurationProvider).AsNoTracking()
Here is what I am getting back currently:
[
{
"teacherid": 2,
"teacherName": "Jane Smith",
"age": 35,
"currentGrade": "One",
"yearsTeaching": 12,
"studentFeedback": [
{
"studentFeedBackId": 12,
"teacherId": 6,
"studentId": 20,
"feedback": "Ms Smith is my favorite teacher"
} ,
{
"studentFeedBackId": 16,
"teacherId": 6,
"studentId": 43,
"feedback": "Ms Smith was so kind to me"
} ,
{
"studentFeedBackId": 21,
"teacherId": 6,
"studentId": 89,
"feedback": "Thank you Mrs Smith for being my teacher. I learned a lot."
}
]
}
]
here is what I want to be getting back:
[
{
"teacherid": 2,
"teacherName": "Jane Smith",
"age": 35,
"currentGrade": "One",
"yearsTeaching": 12,
"studentFeedback": [
{
"studentFeedBackId": 12,
"teacherId": 6,
"studentId": 20,
"feedback": "Ms Smith is my favorite teacher"
}
]
}
]
Thanks #LucianBargaoanu for pointing me in the right direction by saying to have the where in the mapping itself. The solution is to use Parameterization when using Automapper:
The code from this pages shows an example:
string currentUserName = null;
cfg.CreateMap<Course, CourseModel>()
.ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));
and then
dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });
If you want a join you should use Join method in your linq statement. See https://www.tutorialsteacher.com/linq/linq-joining-operator-join . You got exactly what you wrote in your query. Where(u => u.StudentFeedback.Any(x => x.StudentId == userId));
.Any translates to exists.
If you use Net5 EF you can just add a Students property to the Teacher class:
public class Teacher
{
.....
pubilc ICollection<Student> Students { get; set; }
pubilc ICollection<StudentFeedback> StudentFeedbacks { get; set; }
}
You can use query this way:
var query = _context.Teachers.Include(i=> i.StudentFeedbacks)
.Where(t =>
t.TeacherId == teacherId
&& t.StudentFeedbacks.Any(x => x.StudentId == userId))
.ToArray();

Filter with Left Join in Entity Framework

I have User, Contact and ContactOfUser entities in an ASP.NET Core API project. I want to filter users based on input over these tables.
My entity classes are like this:
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(50)]
public string Surname { get; set; }
[MaxLength(60)]
public string Username { get; set; }
}
public class Contact
{
public int Id { get; set; }
[MaxLength(50)]
public string Value{ get; set; }
}
public class ContactOfUser
{
public int Id { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int ContactId { get; set; }
}
I want to get filtered users based on this FilterModel object:
public class FilterModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string Username { get; set; }
public List<int> ContactId { get; set; }
}
How can I make this filtering process in Entity Framework with Linq methods considering that don't apply special filter data when that data will be accepted as null?
I did something like that method but it is not properly working:
List<User> GetFilteredUsers(FilterModel filter)
{
var query1 = dbContext.Users
.Where(u => u.Name.Contains(filter.Name ?? string.Empty) &&
u.Surname.Contains(filter.Surname ?? string.Empty) &&
u.Username.Contains(filter.Username ?? string.Empty));
var query2 = from u in query1
join cu in dbContext.ContactOfUsers on u.Id equals cu.UserId
into res
from item in res.DefaultIfEmpty()
where filter.Contacts.Contains(item.ContactId)
select new InitialUserModel
{
Id = u.Id,
Name = u.Name,
Surname = u.Surname,
Username = u.Username
};
}
You can achieve it in this way
Using GroupBy to get userId and ContactIds accordingly.
var userContactIds = _dbContext.ContactOfUser.GroupBy(p => p.UserId).Select(g => new { UserId = g.UserId, ContactIds = g.Select(p => p.ContactId).ToList() });
Get the result :
var result = _dbContext.User.Select(p => new FilterModel
{
Name = p.Name, Surname = p.Surname, Username = p.Username,
ContactId = userContactIds.Where(c => p.Id == c.UserId).ToList()
});
Updated
List<User> GetFilteredUsers(FilterModel filter)
{
return (from u in _dbContext.User
join c in _dbContext.ContactOfUser on u.Id equals c.UserId
join fContactId in filter.ContactId on c.ContactId equals fContactId
where u.Name == filter.Name && u.Surname == filter.Surname && u.Username == filter.Username
select u).ToList();
}

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>();

RavenDb: querying by a property in related entity

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,
}