I have a code to query/paginate a ProductPrice list... My ProductPrice Object has a Product...
The code works fine...
But looking at log4net I have 2 SELECT happening...
Is that right?
My code :
var query = Session.QueryOver<ProductPrice>();
Product product = null;
query.JoinQueryOver(mg => mg.Product, () => product);
query.WhereRestrictionOn(() => product.Name).IsLike("Asics", MatchMode.Anywhere)
.OrderBy(() => product.Name);
var rowCountQuery = query.ToRowCountQuery();
totalCount = rowCountQuery.FutureValue<int>().Value;
var firstResult = pageIndex * pageSize;
ProductViewModel productViewModel = null;
var productsViewModel = query
.SelectList(l => l
.Select(() => product.Id).WithAlias(() => productViewModel.Id)
.Select(() => product.Name).WithAlias(() => productViewModel.Name)
.Select(mg => mg.Price).WithAlias(() => productViewModel.Price))
.TransformUsing(Transformers.AliasToBean<ProductViewModel>())
.Skip(firstResult)
.Take(pageSize)
.Future<ProductViewModel>();
edited
ProductPrice:
public class ProductPrice : Entity
{
public virtual string Sku { get; set; }
public virtual decimal Price { get; set; }
public virtual Product Product { get; set; }
...
}
Product:
public class ProductPrice : Entity
{
public virtual string Name { get; set; }
public virtual IList<ProductPrice> Prices { get; set; }
...
}
The mapping is generated by Fluent NHibernate...
Thanks
You're doing the ".Value" too soon to get the row count. You should keep it like:
var rowCountQuery = query.ToRowCountQuery();
var rowCount = rowCountQuery.FutureValue<int>();
This way the query is not really executed, just deferred.
After the main query, which seems ok, you may now really fetch the row count integer, and both queries should be sent at the same time to the database:
totalCount = rowCount.Value;
Related
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();
I have a many to many relationship:
A post can have many tags
A tag can have many posts
Models:
public class Post
{
public virtual string Title { get; set; }
public virtual string Content{ get; set; }
public virtual User User { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
I want to count all posts that belong to multiple tags but I don't know how to do this in NHibernate. I am not sure if this is the best way to do this but I used this query in MS SQL:
SELECT COUNT(*)
FROM
(
SELECT Posts.Id FROM Posts
INNER JOIN Users ON Posts.UserId=Users.Id
LEFT JOIN TagsPosts ON Posts.Id=TagsPosts.PostId
LEFT JOIN Tags ON TagsPosts.TagId=Tags.Id
WHERE Users.Username='mr.nuub' AND (Tags.Title in ('c#', 'asp.net-mvc'))
GROUP BY Posts.Id
HAVING COUNT(Posts.Id)=2
)t
But NHibernate does not allow subqueries in the from clause. It would be great if someone could show me how to do this in HQL.
I found a way of how to get this result without a sub query and this works with nHibernate Linq. It was actually not that easy because of the subset of linq expressions which are supported by nHibernate... but anyways
query:
var searchTags = new[] { "C#", "C++" };
var result = session.Query<Post>()
.Select(p => new {
Id = p.Id,
Count = p.Tags.Where(t => searchTags.Contains(t.Title)).Count()
})
.Where(s => s.Count >= 2)
.Count();
It produces the following sql statment:
select cast(count(*) as INT) as col_0_0_
from Posts post0_
where (
select cast(count(*) as INT)
from PostsToTags tags1_, Tags tag2_
where post0_.Id=tags1_.Post_id
and tags1_.Tag_id=tag2_.Id
and (tag2_.Title='C#' or tag2_.Title='C++'))>=2
you should be able to build your user restriction into this, I hope.
The following is my test setup and random data which got generated
public class Post
{
public Post()
{
Tags = new List<Tag>();
}
public virtual void AddTag(Tag tag)
{
this.Tags.Add(tag);
tag.Posts.Add(this);
}
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual int Id { get; set; }
}
public class PostMap : ClassMap<Post>
{
public PostMap()
{
Table("Posts");
Id(p => p.Id).GeneratedBy.Native();
Map(p => p.Content);
Map(p => p.Title);
HasManyToMany<Tag>(map => map.Tags).Cascade.All();
}
}
public class Tag
{
public Tag()
{
Posts = new List<Post>();
}
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual int Id { get; set; }
}
public class TagMap : ClassMap<Tag>
{
public TagMap()
{
Table("Tags");
Id(p => p.Id).GeneratedBy.Native();
Map(p => p.Description);
Map(p => p.Title);
HasManyToMany<Post>(map => map.Posts).LazyLoad().Inverse();
}
}
test run:
var sessionFactory = Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2012
.ConnectionString(#"Server=.\SQLExpress;Database=TestDB;Trusted_Connection=True;")
.ShowSql)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<PostMap>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
using (var session = sessionFactory.OpenSession())
{
var t1 = new Tag() { Title = "C#", Description = "C#" };
session.Save(t1);
var t2 = new Tag() { Title = "C++", Description = "C/C++" };
session.Save(t2);
var t3 = new Tag() { Title = ".Net", Description = "Net" };
session.Save(t3);
var t4 = new Tag() { Title = "Java", Description = "Java" };
session.Save(t4);
var t5 = new Tag() { Title = "lol", Description = "lol" };
session.Save(t5);
var t6 = new Tag() { Title = "rofl", Description = "rofl" };
session.Save(t6);
var tags = session.Query<Tag>().ToList();
var r = new Random();
for (int i = 0; i < 1000; i++)
{
var post = new Post()
{
Title = "Title" + i,
Content = "Something awesome" + i,
};
var manyTags = r.Next(1, 3);
while (post.Tags.Count() < manyTags)
{
var index = r.Next(0, 6);
if (!post.Tags.Contains(tags[index]))
{
post.AddTag(tags[index]);
}
}
session.Save(post);
}
session.Flush();
/* query test */
var searchTags = new[] { "C#", "C++" };
var result = session.Query<Post>()
.Select(p => new {
Id = p.Id,
Count = p.Tags.Where(t => searchTags.Contains(t.Title)).Count()
})
.Where(s => s.Count >= 2)
.Count();
var resultOriginal = session.CreateQuery(#"
SELECT COUNT(*)
FROM
(
SELECT count(Posts.Id)P FROM Posts
LEFT JOIN PostsToTags ON Posts.Id=PostsToTags.Post_id
LEFT JOIN Tags ON PostsToTags.Tag_id=Tags.Id
WHERE Tags.Title in ('c#', 'C++')
GROUP BY Posts.Id
HAVING COUNT(Posts.Id)>=2
)t
").List()[0];
var isEqual = result == (int)resultOriginal;
}
As you can see at the end I do test against your original query (without the users) and it is actually the same count.
In HQL:
var hql = "select count(p) from Post p where p in " +
"(select t.Post from Tag t group by t.Post having count(t.Post) > 1)";
var result = session.Query(hql).UniqueResult<long>();
You can add additional criteria to the subquery if you need to specify tags or other criteria.
Edit : In the future I should read the questions until the last words. I would have seen in HQL...
After some seach, realizing that RowCount removes any grouping in the query ( https://stackoverflow.com/a/8034921/1236044 ). I found a solution using QueryOver and SubQuery which I post here as information.
I find this solution interesting as it offers some modularity, and seprates the counting from the subquery itself, which can be reused as it is.
var searchTags = new[] { "tag1", "tag3" };
var userNames = new[] { "mr.nuub" };
Tag tagAlias = null;
Post postAlias = null;
User userAlias = null;
var postsSubquery =
QueryOver.Of<Post>(() => postAlias)
.JoinAlias(() => postAlias.Tags, () => tagAlias)
.JoinAlias(() => postAlias.User, () => userAlias)
.WhereRestrictionOn(() => tagAlias.Title).IsIn(searchTags)
.AndRestrictionOn(() => userAlias.UserName).IsIn(userNames)
.Where(Restrictions.Gt(Projections.Count<Post>(p => tagAlias.Title), 1));
var numberOfPosts = session.QueryOver<Post>()
.WithSubquery.WhereProperty(p => p.Id).In(postsSubquery.Select(Projections.Group<Post>(p => p.Id)))
.RowCount();
Hope this will help
I have the following parent entity Department which contains a collection of child entities Sections
public class Department
{
private Iesi.Collections.Generic.ISet<Section> _sections;
public Department()
{
_sections = new HashedSet<Section>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<Section> Sections
{
get { return _sections; }
}
public virtual int Version { get; set; }
}
public partial class Section
{
public Section()
{
_employees = new HashedSet<Employee>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual Department Department { get; protected set; }
public virtual int Version { get; set; }
}
I would like to transform (flatten) it to the following DTO
public class SectionViewModel
{
public string DepartmentName { get; set; }
public string SectionName { get; set; }
}
Using the following code.
SectionModel sectionModel = null;
Section sections = null;
var result = _session.QueryOver<Department>().Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property<Department>(d => sections.Department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property<Department>(s => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I am however getting the following exception: could not resolve property: Department.Name of: Domain.Section
I have even tried the following LINQ expression
var result = (from d in _session.Query<Department>()
join s in _session.Query<Section>()
on d.Id equals s.Department.Id into ds
from sm in ds.DefaultIfEmpty()
select new SectionModel
{
DepartmentName = d.Name,
SectionName = sm.Name ?? null
}).ToList();
Mappings
public class DepartmentMap : ClassMapping<Department>
{
public DepartmentMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
Set(x => x.Sections,
m =>
{
m.Access(Accessor.Field);
m.Inverse(true);
m.BatchSize(20);
m.Key(k => { k.Column("DeptId"); k.NotNullable(true); });
m.Table("Section");
m.Cascade( Cascade.All | Cascade.DeleteOrphans);
},
ce => ce.OneToMany());
}
}
public class SectionMap : ClassMapping<Section>
{
public SectionMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
ManyToOne(x => x.Department,
m =>
{
m.Column("DeptId");
m.NotNullable(true);
});
}
}
But this throws a method or operation is not implemented.
Seeking guidance on what I am doing wrong or missing.
NHibernate doesn't know how to access a child property's child through the parent entity. A useful thing to remember about QueryOver is that it gets translated directly into SQL. You couldn't write the following SQL:
select [Section].[Department].[Name]
right? Therefore you can't do the same thing in QueryOver. I would create an alias for the Department entity you start on and use that in your projection list:
Department department;
Section sections;
var result = _session.QueryOver<Department>(() => department)
.Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property(() => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I noticed in your comment you'd like an order by clause. Let me know if you need help with that and I can probably come up with it.
Hope that helps!
This may be now fixed in 3.3.3. Look for
New Feature
[NH-2986] - Add ability to include collections into projections
Not sure but if this is your problem specifically but if you are not using 3.3.3 then upgrade and check it out.
Aslo check out the JIRA
Have you tried a linq query like
from d in Departments
from s in d.Sections
select new SectionModel
{
DepartmentName = d.Name,
SectionName = s == null ? String.Empty : s.Name
}
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,
}
I'm trying to return multiple entities from a QueryOver query. I'm doing this code in a plain text editor so there may be syntactic errors, but it should get the idea across.
public class Product
{
public virtual int ID { get; set; }
public virtual string ProductName { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Inventory> Inventories { get; set; }
...
}
public class Category
{
public virtual int ID { get; set; }
public virtual string CategoryName { get; set; }
public virtual string Style { get; set; }
public virtual Product Product { get; set; }
...
}
public class Inventory
{
public virtual int ID { get; set; }
public virtual List<Discount> Discounts { get; set; }
public virtual Product Product { get; set; }
public virtual bool InStock { get; set; }
...
}
public class Discount
{
public virtual int ID { get; set; }
public virtual Inventory Inventory { get; set; }
public virtual decimal DiscountAmount { get; set; }
...
}
Now my goal is to take a product ID and a couple other options to pull back a Category, Inventory, and DiscountAmount in a single query. I've gotten this to work using HQL with this query:
var query = session.CreateQuery("select category, inventory, discount.DiscountAmount"
+ " from Product product"
+ " join product.Categories category"
+ " join product.Inventories inventory"
+ " left join inventory.Discounts discount"
+ " where product.ID = :productID"
+ " and category.Style = :style"
+ " and inventory.InStock = 1");
With this query I get an list of object arrays that each have a Category entity, Inventory entity, and a DiscountAmount decimal. My goal is to use a QueryOver query to do this same query with no magic strings, but I can't get it to work. Here's what I've tried so far:
Product productAlias = null;
Category categoryAlias = null;
...
var query = session.QueryOver<Product>(() => productAlias)
.Where(() => productAlias.ID == productID)
.JoinAlias(() => productAlias.Categories, () => categoryAlias)
...
.Select(Projections.Property(() => categoryAlias.ID),
Projections.Property(() => discountAlias.Inventory),
Projections.Property(() => discount.DiscountAmount));
This query only pulls back the ID for Category, and while it does pull the full Inventory entity back it uses a full additional database query to grab it.
...
.Select(Projections.Property(() => categoryAlias),
Projections.Property(() => inventoryAlias),
Projections.Property(() => discountAlias.DiscountAmount));
This query throws a runtime exception of "Could not resolve property: categoryAlias of : Product".
...
.Select(Projections.Property(() => categoryAlias.ID).WithAlias(() => ReturnClass.Category),
Projections.Property(() => inventoryAlias.ID).WithAlias(() => ReturnClass.Inventory),
Projections.Property(() => discountAlias.DiscountAmount).WithAlias(() => ReturnClass.DiscountAmount))
.TransformUsing(Transformers.AliasToBean<ReturnClass>());
This query throws a runtime exception of "Object of type Int32 cannot be converted to type Category".
...
.Select(Projections.Property(() => categoryAlias.ID)
.TransformUsing(Transformers.AliasToBean<Category>())
This query returns a default Category entity.
So is there any way to mimic the HQL query using the QueryOver API, or is my only option to choose between HQL or making multiple queries?
Edit: To be more clear, I really want to avoid magic strings as much as possible, so I'd really prefer strongly typed QueryOver queries. Currently I'm using a QueryOver query that returns the IDs for the Category and Inventory entities and then querying for them separately, but since I have to hit those tables in the first query anyway I'd rather return them all at once.
Edit 2: The exact SQL I'm trying to achieve is
Select Category.ID, Category.CategoryName, Category.Style, (other Category columns),
Inventory.ID, Inventory.InStock, (other Inventory columns),
Discount.DiscountAmount
From Products as Product
Inner join Categories as Category ...
Where Product.ID = #productID
And Category.Style = #style
And ...
I think you should use Fetch instead of JoinAlias:
.Fetch(product => product.Categories).Eager
and don't use select: .List<Product>() // then by LINQ you can get Categories and Inventories from Product
So here is an example of "eager" loading all of the subcategories for my categories.
No N+1 when iterating over the categories collection. Future is the key here.
Category catalias = null;
var subCategories =_session.QueryOver<Category>().JoinQueryOver(x => x.SubCategories, () => catalias, JoinType.LeftOuterJoin).
Future<Category>();
var categories = _session.QueryOver<Category>().Where(x => x.ParentCategoryId == null).Future<Category>();