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

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/

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.

RavenDb Select() downcasts instead of selecting the neccessary fields

public class PersonBrief
{
public int Id { get; set; }
public string Picture { get; set; }
public PersonBrief(Person person)
{
Id = person.Id;
Picture = person.Picture;
}
}
public class Person : PersonBrief
{
public string FullName { get; set; }
}
var results = session.Query<Person>()
.Select(x => new PersonBrief(x))
.ToList();
Assert.IsNull(results[0] as Person); // Fails
Is this a bug? If not, what would be the correct way to select only the fields i'm interested in?
It would work if you move the .ToList before the .Select, but that would be doing the work on the client.
If you want to do it on the server, you need to use As in your query, and you need a static index that does a TransformResults. See these docs.

Loading navigation properties in NHibernate 3.2

I have these entities:
public class Post {
public virtual int Id { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Tag {
public virtual int Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Comment {
public virtual int Id { get; set; }
public virtual Post Post { get; set; }
}
I want to load a Post by it's related Tags and related Comments's count by LINQ. I use this:
var posts = session
.Query<Post>()
.OrderBy(t => t.Id)
.Select(t => new {
Post = t,
CommentsCount = t.Comments.Count(),
Tags = t.Tags
}).ToList();
Do you think it is enough? or do you have any suggestion that may be better than my code? thanks.
This highly depends on your mapping and what you want to do with the fetched result. Imho eager loading Tags would speed up performance (Select N+1) if you want to access the tags list (assuming that the tags are mapped as lazy).
var posts = session
.Query<Post>()
.Fetch(t => t.Tags)
.OrderBy(t => t.Id)
.Select(t => new {
Post = t,
CommentsCount = t.Comments.Count(),
Tags = t.Tags
}).ToList();
http://ayende.com/blog/1328/combating-the-select-n-1-problem-in-nhibernate

Fluent NHibernate one to many not saving children

I am using Fluent NHibernate. This is a classic case of a one to many relationship. I have one Supply parent with many SupplyAmount children.
The Supply parent object is saving with correct info, but the amounts are not getting inserted into the db when I save the parent. What am I doing for the cascade not to work?
The entities are as follows:
public class Supply : BaseEntity
{
public Guid SupplyId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Comments { get; set; }
public virtual IList<SupplyAmount> Amounts { get; set; }
public Supply()
{
Amounts = new List<SupplyAmount>();
}
public virtual void AddAmount(SupplyAmount amount)
{
amount.Supply = this;
Amounts.Add(amount);
}
}
public class SupplyAmount : BaseEntity
{
public virtual Guid SupplymountId { get; set; }
public virtual Supply Supply { get; set; }
public virtual int Amount { get; set; }
}
And the mapping as follows:
public class SupplyMap : ClassMap<Supply>
{
public SupplyMap()
{
Id(x => x.SupplyId);
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.Comments);
HasMany<SupplyAmount>(x => x.Amounts)
.Inverse().Cascade.SaveUpdate()
.KeyColumn("SupplyAmountId")
.AsBag();
}
}
public class SupplyAmountMap : ClassMap<SupplyAmount>
{
public SupplyAmountMap()
{
Id(x => x.SupplyAmountId);
References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Map(x => x.Amount);
}
}
And this is how I call it:
public SaveIt()
{
Supply sOrder = Supply();
sOrder.FirstName = "TestFirst";
sOrder.LastName = "TestLast";
sOrder.Comments = "TestComments";
for (int i = 0; i < 5; i++)
{
SupplyAmount amount = new SupplyAmount();
amount.Amount = 50;
amount.Supply = sOrder;
sOrder.AddAmount(amount);
}
// This call saves the Supply to the Supply table but none of the Amounts
// to the SupplyAmount table.
AddSupplyOrder(sOrder);
}
I know this is an old post but why not...
// This call saves the Supply to the Supply table but none of the Amounts
This comment in SaveIt() indicates you call the save on the Supply and not the amounts.
In this case you have your logic the wrong way around.
So to fix this:
SupplyMap -> The Inverse shouldn't be there for Amounts.
HasMany<SupplyAmount>(x => x.Amounts).Cascade.SaveUpdate();
SupplyAmountMap ->
remove References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Replace it with
References<Supply>(x=>x.Supply);
You should now be right to call the save on your supply object only and it will cascade down to the amounts.
Session.Save(supply);
In your test after you have arrange the supply and supplyamount make sure you call a
Session.Flush()
after your save to force it in.
This isn't as important in code as you will usually run in transactions before recalling the supply object.
Cheers,
Choco
Also as a side note it usually not a good idea to be to verbose with fluentmappings. let the default stuff do it thing which is why I would recommend against the column naming hints.

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.