I have a problem with NHibernate not using my mappings configuration for eager loading a collection when I get something using HQL or Linq (Session.Query). Session.Get and Session.QueryOver is working like expected.
I'm using NHibernate 3.2. Here's the mapping of a collection in my Product mapping.
<bag name="OrderItems" inverse="true" cascade="none" lazy="false" fetch="join">
<key column="order_id" />
<one-to-many class="OrderItem" />
</bag>
and from the other side the mapping looks like this:
<many-to-one name="Product" class="Product" column="product_id" not-null="true" />
I have 4 Tests, 2 are successfull and 2 are not. They use Session.SessionFactory.Statistics to keep track of CollectionFetchCount (was OrderItems selected in 1 joined query or in a separate). The intent is to have OrderItems selected and loaded when selecting the product as OrderItems are almost always accessed as well.
LastCreated is a simple reference to the last product inserted into the DB.
[Test] /* Success */
public void Accessing_Collection_Using_Session_Get_Results_In_1_Select()
{
// Get by Id
var product = Session.Get<Product>(LastCreated.Id);
var count = product.OrderItems.Count;
Assert.AreEqual(0,statistics.CollectionFetchCount,"Product collectionfetchcount using Get");
}
[Test] /* Success */
public void Accessing_Collection_Using_Session_QueryOver_Results_In_1_Select()
{
// Get by Id
var product = Session.QueryOver<Product>().SingleOrDefault();
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using QueryOver");
}
[Test] /* Fail */
public void Accessing_Collection_Using_Session_Query_Results_In_1_Select()
{
// Get by IQueryable and Linq
var product = Session.Query<Product>().Single(x => x.Id == LastCreated.Id);
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using Linq");
}
[Test] /* Fail */
public void Accessing_Collection_Using_HQL_Results_In_1_Select()
{
// Get by IQueryable and Linq
var product = Session.CreateQuery("from Product where Id = :id")
.SetParameter("id",LastCreated.Id)
.UniqueResult<Product>();
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using HQL");
}
Is this intended behaviour or am I doing something wrong?
HQL queries will not respect a fetch="join" set in mapping. This is because they are freeform queries, making it impossible for NH to guess how to transform them to add the join.
Linq is implemented as a wrapper for HQL, QueryOver is a wrapper for Criteria; that's why you see the different behaviors.
If you need eager loads in Linq/HQL, you will have to make them explicit in the query (using join fetch and Fetch()/FetchMany()
Related
I am using the repository pattern with Entity Framework as described in this article: repository pattern with Entity Framework
In the part where the GenericRepository is described (Generic Repository) there is a method which is used to get entities from the database set called Get. It has an orderBy but no groupBy. I am wondering how one might implement a groupBy in the same manner as the orderBy so that you can specify which field to group by dynamically on the entity.
What I have come up with is this:
Func<IQueryable<TEntity>, IGrouping<string, TEntity>> groupBy = null
and then in the method code it should be used something like this:
if(groupBy != null)
{
query = groupBy(query).ToList();
}
But this is not compiling since the IGrouping is not queryable. Does someone know how to point me in the right direction or has a solution to this?
Edit: The reason for doing this instead of using groupby on the returned list is for performance reasons. I want the groupby to be sent as an sql statement to the database and resolved there.
Grouping has no sense without projection. So you have to define new method which returns IEnumerable with new type.
I have added sample of such method. Also removed includeProperties because EF Core ignores Includes during grouping.
Usage sample:
_orderRepostory
.GetGrouped(e => e.UserId, g => new { UserId = g.Key, Count = g.Count()});
And implementation:
public class GenericRepository<TEntity> where TEntity : class
{
... // other code
public virtual IEnumerable<TResult> GetGrouped<TKey, TResult>(
Expression<Func<TEntity, TKey>> groupingKey,
Expression<Func<IGrouping<TKey, TEntity>, TResult>> resultSelector,
Expression<Func<TEntity, bool>>? filter = null)
{
var query = dbSet.AsQueryable();
if (filter != null)
{
query = query.Where(filter);
}
return query.GroupBy(groupingKey).Select(resultSelector);
}
}
I'm doing the below join, there are many bookingActions records, but I want there to only be one BookingAction record per booking record. I want the BookingAction record that has the highest primary key value.
How would I do this?
var bookingLocationsQuery = (
from
booking in session.Query<Booking>()
join
bookingActions in session.Query<BookingAction>() on booking.Id equals bookingActions.bookingId
where
(booking.bookingAdminID == userId)
select new { booking, bookingActions }
);
A couple of suggestions. First, you should be leveraging NHibernate's many-to-one to do the join for you instead of doing it manually. It looks like you currently have something like this...
public class BookingAction
{
// ... other properties ...
public virtual int bookingId { get; set; }
}
<class name="BookingAction">
<!-- ... other properties ... -->
<property name="bookingId" />
</class>
Don't do that. Instead, you should have:
public class BookingAction
{
// ... other properties ...
public virtual Booking Booking { get; set; }
}
<class name="BookingAction">
<!-- ... other properties ... -->
<many-to-one name="Booking" column="bookingId" />
</class>
Similar advice for Booking.bookingAdminID. It should be a many-to-one to User, not just a simple property.
Second, after you make those changes, you should be able to accomplish your goal with a query like this:
var subquery = session.Query<BookingAction>()
.Where(a => a.Booking.Admin.Id == userId)
.GroupBy(a => a.Booking.Id)
.Select(g => g.Max(a => a.Id));
var bookingActions = session.Query<BookingAction>()
.Fetch(a => a.Booking)
.Where(a => subquery.Contains(a.Id));
Sorry about switching it to the chained extension method syntax - that's easier for me to work with. It's exactly equivalent to the from ... select syntax in execution.
Try using the Max() method, for sample:
var bookingLocation = session.Query<Booking>()
.Where(booking => booking.bookingAdminID == userId)
.Max(x => booking.bookingAdminID);
I am working with code first approach in EDM and facing an error for which I can't the solution.Pls help me
LINQ to Entities does not recognize the method 'Boolean
CheckMeetingSettings(Int64, Int64)' method, and this method cannot be
translated into a store expression.
My code is following(this is the query which I have written
from per in obj.tempPersonConferenceDbSet
where per.Conference.Id == 2
select new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327,per.Person.Id)
}
public bool CheckMeetingSettings(int,int)
{
///code I have written.
}
Please help me out of this.
EF can not convert custom code to SQL. Try iterating the result set and assigning the property outside the LINQ query.
var people = (from per in obj.tempPersonConferenceDbSet
where per.Conference.Id == 2
order by /**/
select new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
}).Skip(/*records count to skip*/)
.Take(/*records count to retrieve*/)
.ToList();
people.ForEach(p => p.CanSendMeetingRequest = CheckMeetingSettings(6327, p.Id));
With Entity Framework, you cannot mix code that runs on the database server with code that runs inside the application. The only way you could write a query like this, is if you defined a function inside SQL Server to implement the code that you've written.
More information on how to expose that function to LINQ to Entities can be found here.
Alternatively, you would have to call CheckMeetingSettings outside the initial query, as Eranga demonstrated.
Try:
var personDetails = obj.tempPersonConferenceDbSet.Where(p=>p.ConferenceId == 2).AsEnumerable().Select(p=> new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327,per.Person.Id)
});
public bool CheckMeetingSettings(int,int)
{
///code I have written.
}
You must use AsEnumerable() so you can preform CheckMeetingSettings.
Linq to Entities can't translate your custom code into a SQL query.
You might consider first selecting only the database columns, then add a .ToList() to force the query to resolve. After you have those results you van do another select where you add the information from your CheckMeetingSettings method.
I'm more comfortable with the fluid syntax so I've used that in the following example.
var query = obj.tempPersonConferenceDbSet
.Where(per => per.Conference.Id == 2).Select(per => new { Id = per.Person.Id, JobTitle = per.Person.JobTitle })
.ToList()
.Select(per => new PersonDetails { Id = per.Id,
JobTitle = per.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327, per.Person.Id) })
If your CheckMeetingSettings method also accesses the database you might want to consider not using a seperate method to prevent a SELECT N+1 scenario and try to express the logic as part of the query in terms that the database can understand.
I'm getting a strange error when running what appears to be a simple query.
return (from x in session.Query<Contact>()
.Where(x => x.Id == 10)
select new ContactIndexViewModel
{
Id = x.Id,
Name = x.BasicInfo.FirstName + " " + x.BasicInfo.LastName,
Filters = x.Filters
}).FirstOrDefault();
Is generating the following SQL
select
contact0_.[Id] as col_0_0_,
contact0_.[BasicInfoFirstName] as col_1_0_,
contact0_.[BasicInfoLastName] as col_2_0_,
. as col_3_0_,
filters1_.[Id] as column1_16_,
filters1_.Criteria1 as Criteria2_16_,
// .. .more filters1_ fields
filters1_.ContactId as ContactId16_
from
[MyServer].[dbo].[Contact] contact0_
inner join [MyServer].[dbo].[Filter] filters1_
on contact0_.[Id]=filters1_.ContactId
where
contact0_.[Id]=#p0
Notice the fourth column being selected. BasicInfo is a component and the select (in the query) includes all the fields defined in the ViewModel.
I am not having any other problems with the Contact or Filter objects in other parts of the application. Contact -> Filter has a one to many relationship.
Any idea's on how to debug or what may cause this?
UPDATE
If I remove the reference to Filters in the select, the problem goes away.
UPDATE Relevant Mappings
Contact
public partial class ContactMap : ClassMap<Contact>
{
/// <summary>Initializes a new instance of the <see cref="ContactMap"/> class.</summary>
public ContactMap()
{
Table("[MyServer].[dbo].[Contact]");
OptimisticLock.Version();
DynamicUpdate();
LazyLoad();
Id(x=>x.Id)
.Access.CamelCaseField(Prefix.Underscore)
.Column("[Id]")
.GeneratedBy.Identity();
Version(x=>x.RecordVersion)
.Access.CamelCaseField(Prefix.Underscore)
.Column("[RecordVersion]")
.CustomSqlType("timestamp")
.Not.Nullable()
.UnsavedValue("null")
.CustomType("BinaryBlob")
.Generated.Always();
Map(x=>x.Active).Access.CamelCaseField(Prefix.Underscore);
// other scalar properties
Component(x0=>x0.BasicInfo, m0=>
{
m0.Map(x1=>x1.FirstName).Column("[BasicInfoFirstName]").Access.CamelCaseField(Prefix.Underscore);
m0.Map(x1=>x1.LastName).Column("[BasicInfoLastName]").Access.CamelCaseField(Prefix.Underscore);
// other scalar properties
});
// other relationships
HasMany(x=>x.Searches)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse()
.LazyLoad()
.KeyColumns.Add("ContactId");
}
}
Search
public partial class SearchMap : ClassMap<Search>
{
public SearchMap()
{
Table("[MyServer].[dbo].[Search]");
OptimisticLock.Version();
DynamicUpdate();
LazyLoad();
Id(x=>x.Id)
.Access.CamelCaseField(Prefix.Underscore)
.Column("[Id]")
.GeneratedBy.Identity();
Map(x=>x.Controller).Not.Nullable().Access.CamelCaseField(Prefix.Underscore);
Map(x=>x.Module).Not.Nullable().Access.CamelCaseField(Prefix.Underscore);
Map(x=>x.Name).Column("[Name]").Not.Nullable().Access.CamelCaseField(Prefix.Underscore);
References(x=>x.Contact)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.All()
.Fetch.Select()
.Columns("ContactId");
HasMany(x=>x.DataFilters)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse()
.LazyLoad()
.KeyColumns.Add("SearchId");
}
}
Did you map Filters with FetchMode.Join?
By the way, it may be easier to create the ContactIndexViewModel in memory, with the trade off that it fetches too many columns from the database. On the other side, Get doesn't flush the session, which may be performance relevant.
var contact = session.Get<Contact>(10);
return new ContactIndexViewModel
{
Id = contact.Id,
Name = contact.BasicInfo.FirstName + " " + contact.BasicInfo.LastName,
Filters = contact.Filters
};
Your mapping for the table is unusual to me.
Table("[MyServer].[dbo].[Contact]");
Normally the server name is provided during configuration, the schema is stated separately, and the delimiters ("[...]") are set by NHibernate. I would map it as:
Schema("dbo");
Table("Contact");
That may be causing a parsing problem leading to the odd select. If that's not it, then I think it's a bug -- NHibernate should never issue a select without a table alias and column name.
I have a MVC 2 project, using Entity Framework, in Visual Studio 2010 and I have a class 'ProductModel' which is doing a LINQ query to return an product from the database.
I want to return Products objects instead of the default "entities" query objects so I founded that I had to do like this:
var product = from x in productosBD.Products
where x.Id == id
select new ProductoModels { Id = x.Id, NombreCorto = x.NombreCorto, NombreLargo = x.NombreLargo, Pvp = x.Pvp .... };
The problem is that I have to do ALL the assignations with all the attributes of the database table (could be 30 easily) So my question is : Is there any way to do a mapping of the database entities with my objects class automatically? Something like:
var product = from xin productosBD.Products
where x.Id == id
select x;
but retorning a Products object?
Thanks in advance
Automapper : http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
public static void Configure() {
Mapper.CreateMap<Order, OrderViewModel>();
Mapper.CreateMap<OrderLineItem, OrderLineItemViewModel>();
}
var viewModel = Mapper.Map<Order, OrderViewModel>(order);