Trying to Query RavenDB Entity with a Collection via Index - ravendb

I have a RavenDB mvc applicaton that has a document entity called Member. Each Member document has a list of users that are considered administrators. Only they can view and manage that Member document. On one of the pages I have a member search and have created an index to assist in the search.
public class Members_ByName : AbstractIndexCreationTask<Member>
{
public Members_ByName()
{
Map = members => from member in members select new {member.Title};
Indexes.Add(x => x.Title, FieldIndexing.Analyzed);
Sort(x => x.Title, SortOptions.String);
}
}
public class UserReference
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Member
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<UserReference> Administrators { get; set; }
}
Since the user can only view/manage Member documents where they are an Administrator when I do the following to get the members
RavenQueryStatistics stats;
var query = RavenSession.Query<Member, Members_ByName>().Statistics(out stats);
query = query.Where(x => x.Title.StartsWith("anything"));
query = query.Where(x => x.Administrators.Any(y => y.Id == CurrentUser.Id));
var list = query.OrderBy(x => x.Title).Paging(CurrentPage, Configuration.DefaultPage, CurrentPageSize).ToList();
When the above code runs I get "The field 'Administrators_Id' is not indexed, cannot query on fields that are not indexed" which I understand but every thing I have attempted to get Administrator's Id in the index has not worked and not sure how to make it work at this point.

Try this:
Map = members => from member in members
select new {member.Title, Administrators_Id = members.Administrators.Select(x=>x.Id)};

Related

Get value in 1-to-many relationship (EF Core)

I'm seeking an equivalent to this SQL query in EF Core:
SELECT Id, Name, (Select AdminRoleId From EventAdmins Where EventId = Events.Id And AdminId = [value from cookie]) As EventRoleId From Events
This is what I have so far:
public IList<Event> Events { get; set; }
public IList<EventAdmin> EventAdmins { get; set; }
public async Task<IActionResult> OnGetAsync() {
var adminId = Guid.Parse(Request.Cookies["Adm_AdminId"]);
Events = await _context.Events.SelectMany(e => e.EventAdmins.Where(x => x.EventId == e.Id && x.AdminId == adminId).Select(x => x.AdminRoleId)).AsNoTracking().ToListAsync();
return Page();
}
I'm not sure what's wrong, but I get an error saying "Error CS0452: The type 'Guid' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method".
Event model:
public class Event {
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Id")]
public IList<EventAdmin> EventAdmins { get; set; }
}
EventAdmin model:
public class EventAdmin {
public Guid Id { get; set; }
public Guid EventId { get; set; }
public Guid AdminId { get; set; }
public Guid AdminRoleId { get; set; }
[ForeignKey("EventId")]
public Event Events { get; set; }
}
Reason
This error occurs because you're trying to ask EF Core not to track a list of GUID. However, the list of Guid is a list of value types.
As you know, EF Core can only track a series of reference type, so the method signature of AsNoTracking<TEntity>() is :
public static IQueryable<TEntity> AsNoTracking<TEntity> (this IQueryable<TEntity> source)
where TEntity : class;
Note the constraints of where TEntity : class.
In other words, you can never invoke AsNoTracking<Guid>():
Events = await _context.Events
.SelectMany(e => e.EventAdmins.Where(x => x.EventId == e.Id).Select(x => x.AdminRoleId))
.AsNoTracking() // Actually, it will invoke `AsNoTracking<Guid>()`
.ToListAsync();
How to Fix
Your SQL doesn't seem valid. I guess you want to return a {Id, Name, EventRoleId}.
If you would like to do that with SelectMany, you could simply query as below:
var Events = await this._context.Events
.SelectMany(
e => e.EventAdmins.Where(x => x.EventId == e.Id).Select(x => x.AdminRoleId),
(p,g) => new {
Id = p.Id,
Name = p.Name,
EventRoleId = g
}
)
// .AsNoTracking()
.ToListAsync();
There's no need to call .AsNoTracking() at all. Because no tracking is performed if the result set does not contain any entity types.
As a side note, you shouldn't decorate the Event.EventAdmins with a [ForeignKey("Id")] attribute :
public class Event {
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Id")]
public IList EventAdmins { get; set; }
}
Because the Event is the principal entity and the EventAdmin is the dependent entity. Only the EventAdmin has a foreign key that references the Event.
Your LINQ query doesn't seem to include adminId so I can't see how it could work. Try something like this:
var eventAdmin = _context.EventAdmin.SingleOrDefault( e => e.Id == adminId);
var events = eventAdmin.Events;
Try using a tool like Linqpad to dissect your queries one step at a time.

Query entries by nested collection elements in RavenDB

I'm new to RavenDB and I'm struggling with this simple (i guess) issue.
I have a Subscriber with a collection of Subscriptions. And I want to make search by Subscription's fields, and return related Subscriber.
Here are simplified class examples:
public class Subscriber
{
public string Email { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public List<Subscription> Subscriptions { get; set; }
}
public class Subscription
{
public Guid Id { get; set; }
public string EventType { get; set; }
}
I've tried to make an index, as it is said in RavenDB docs:
public class Subscriber_BySubscription : AbstractIndexCreationTask<Subscriber>
{
public Subscriber_BySubscription()
{
Map = subscribers => from subscriber in subscribers
from subscription in subscriber.Subscriptions
select new
{
subscription.EventType,
subscription.QueueName
};
}
}
But I'm not sure that this is what I need, since query by collection using Select and Contains doesn't work. Moreover, the code looks so ugly that I feel that this is not the way how it should be.
So, I'd like to query Subscriptions by EventType, and have corresponding Subscriber as a result. In LINQ it would look like this: subscribers.Where(x => x.Subscriptions.Select(c => c.EventType).Contains(myEventType))
Managed to do it. Here is the right index:
public class Subscriber_BySubscription : AbstractIndexCreationTask<Subscriber>
{
public class Result
{
public string EventType { get; set; }
}
public Subscriber_BySubscription()
{
Map = subscribers => from subscriber in subscribers
from subscription in subscriber.Subscriptions
select new
{
subscription.EventType
};
}
}
And that's how it should be used:
var results = uow.Session
.Query<Subscriber_BySubscription.Result, Subscriber_BySubscription>()
.Where(x => x.EventType == eventType)
.OfType<Subscriber>()
.ToList();

Transformations Filter

I have the following Map / Transform
public class PositionSearch : AbstractIndexCreationTask<Employer>
{
public PositionSearch()
{
Map = employers =>
from employer in employers
from position in employer.Positions
select new
{
EmployerName = employer.Name,
SearchSkills = position.RequiredSkills
.Select(x => x.Skill)
};
TransformResults = (database, results) =>
from result in results
from position in result.Positions
select new
{
EmployerId = result.Id,
EmployerName = result.Name,
PositionId = position.Id,
PositionTitle = position.Title,
RequiredSkills = position.RequiredSkills
.Select(x => new { x.Skill, x.Proficiency })
};
// Any field you are going to use .Search() on should be analyzed.
Index("SearchSkills", FieldIndexing.Analyzed);
}
}
I have an employer object with two positions, each with a single skill, "NH" and "MVC"
When I execute the following query I'm getting two position results returned, when I expected one.
Can anyone tell me why this is behaving this way? I've got a feeling it's something to do with a join i'm performing, but I'm not sure.
using (var session = DocumentStore.OpenSession())
{
var results = session.Query<PositionSearchResultModel, PositionSearch>()
.Customize(x => x.WaitForNonStaleResults())
.Search(x => x.SearchSkills, "NH")
.OfType<PositionSearchResultModel>().ToList();
Assert.AreEqual(1, results.Count());
}
I'm wanting to use transform so that I can access the Temp-Index-Score meta data for ordering, I've been unable to access the meta data without a transform so far.
You are indexing the Employer document. The search found a document that contained the skill in question, and then you asked to transform the document.
The way you had it before is projecting from the index, which is the only way you are going to get the specific position found as part of your results.
I really think you would be happier with Position as it's own document...
public class Employer
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Position
{
public string Id { get; set; }
public string EmployerId { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public ICollection<SkillProficiency> RequiredSkills { get; set; }
}
This may seem more relational in thinking, but it works just fine in RavenDB, and it will be much easier to query than what you are doing now.

LINQ-to-NHibernate: Cannot use Linq Skip() and Take() with FetchMany

I have these entities:
public class BlogPost {
public virtual int Id { get; set; }
public virtual IList<Keyword> Keywords { get; set; }
public virtual IList<BlogComment> Comments { get; set; }
}
public class BlogComment {
public virtual int Id { get; set; }
public virtual BlogPost Post { get; set; }
}
public class Keyword {
public virtual int Id { get; set; }
public virtual IList<BlogPost> BlogPosts { get; set; }
}
I want to load a paged-list of BlogPosts by their Keywords and comments-count. So I try this:
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
But the folowing error occurs:
Specified method is not supported.
And when I remove .Skip((pageNumber - 1) * pageSize).Take(pageSize) it works! e.g.
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
// remove the below line
//.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
Have you any idea please to take a number of rows by including Keywords? Thanks for any suggestion.
I'm using NHibernate 3.2 mapping by code.
The problem is that the nhibernate linq provider isn't fully implemented yet.
You could move the skip / take calls to be after the ToList() but then you're going to be filtering on the entire result set rather than querying specifically for the records matching that range.
Alternatively you could use the QueryOver<> api which has proper support for Take and Skip as per this answer: https://stackoverflow.com/a/5073510/493
This should now be supported in 3.3.3.GA
http://sourceforge.net/p/nhibernate/news/2013/03/nhiberate-333ga-released/

get property count with entity using nhibernate

can I am hoping someone can point me to the right direction on how to get count of a property and the entity using a single trip to sql.
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public virtual int ArticlesCount { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
Cache.ReadWrite();
}
}
My goal is to get the all Categories and the count of the associated articles if there is any.
I have tried this
ICriteria crit = session.CreateCriteria(typeof(Category));
crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Description"), "Description")
.Add(Projections.Count("Articles"), "ArticlesCount"));
crit.SetResultTransformer(Transformers.AliasToBean (typeof(Category)));
var aa=crit.List();
unfortunately the generated sql shows the count of the Category table not the Articles list.
Thanks
You could use a multi-query, multiple sql statements but it is one trip to the database.
Here is an example from the nhibernate documentation:
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/performance.html
IMultiQuery multiQuery = s.CreateMultiQuery()
.Add(s.CreateQuery("from Item i where i.Id > ?")
.SetInt32(0, 50).SetFirstResult(10))
.Add(s.CreateQuery("select count(*) from Item i where i.Id > ?")
.SetInt32(0, 50));
IList results = multiQuery.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Maybe not exactly what you were thinking.