I'm aware that lazy-loading 'on' is the default setting in NHibernate. I switched lazy-loading off, using mapping-by-code, for the entity (Student) and the collection (Comments) contained within an entity. However a test including the use of SQL-Profiler shows that it does not load the collection from the database when the entity is accessed via a Session.Get(). I see only a 'Select' to get the entity (Student) from the Db. No 'Join' or 'Selects' to the collection table (Comments). Am I missing something? I'm using NH version 5.
Mapping:
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;
namespace Infrastructure.Repository.NH.Tests
{
public class StudentSubclassMapping: JoinedSubclassMapping<Student>
{
public StudentSubclassMapping()
{
Lazy(false);
Property(student => student.EnrollmentDate);
List(student => student.Comments,
listMapper =>
{ listMapper.Lazy(CollectionLazy.NoLazy);},
relationMapper =>
relationMapper.Element());
}
}
}
Domain:
public class Student : Contact
{
public virtual DateTime? EnrollmentDate { get; set; }
public virtual IList<string> Comments { get; set; }
}
Test:
public void Get_TestToCheckIfLazyLoadingIsTurnedOff()
{
using (var session = SessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var student = session.Get<Student>(2);
transaction.Commit();
}
}
}
Just tested this with NHibernate 5.0.3 and it seems to be working properly:
NHibernate: SELECT student0_.student_key as id1_0_0_, student0_.EnrollmentDate as enrollmentdate2_1_0_ FROM Student student0_ inner join Contact student0_1_ on student0_.student_key=student0_1_.Id WHERE student0_.student_key=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
NHibernate: SELECT comments0_.student_key as student1_2_0_, comments0_.id as id2_2_0_, comments0_.idx as idx3_0_ FROM Comments comments0_ WHERE comments0_.student_key=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
The listMapper.Lazy(CollectionLazy.NoLazy) that you already have there should do the trick.
I think that maybe you don't really have a Student with ID 2 in the database?
If that's the case, you will only see NHibernate issuing the first query (over Contact/Student) and it will not issue a query over Comments as the Student does not exist.
If you have a Student with ID 2 you should see the query to the Comments table right after the initial one.
If you like, you can try adding listMapper.Fetch(CollectionFetchMode.Join) to bring both the Student and Comments in the same query, although I wouldn't generally recommend this.
Related
I have class Cycle that contains list of employees. Employee has User property that is reference to User and user has property IsDeleted because we use soft delete. We use HasQueryFilter(e => !e.IsDeleted) to prevent getting deleted user. With Include this works perfectly, but because of performance issues in EF 3.1 we started using Load. Everything is much faster but HasQueryFilter is not applied and I get Employee with User that is null. Is HasQueryFilter not applicable with load?
public class Employee
{
...
public ICollection<CycleEmployee> Employees { get; protected set; } = new List<CycleEmployee>();
}
public class CycleEmployee
{
public User User { get; protected set; }
public Guid UserId { get; protected set; }
}
And in CycleEmployee I get UserId always, but User prop is null if User is soft deleted
Fetching data with load:
public Cycle GetCycle(Guid cycleId, params string[] includeProperties)
{
var query = DbContext
.Cycles
.Where(e => e.OrganizationId == CurrentUser.OrganizationId);
var cycle = query.SingleOrDefault(e => e.Id == cycleId);
query.LoadMultipleProperties(includeProperties);
return cycle;
}
public static void LoadMultipleProperties<T>(this IQueryable<T> query, params string[] includeProperties)
where T : class
{
foreach (var prop in includeProperties)
{
query.Include(prop).Load();
}
}
Edit
When using include: List of employees is empty(because User inside Employee is soft deleted) - expected result
When using load: List of employees is not empty(I get Employee entities where User is null but UserId is not null) - problem
If anyone else has similar problem follow this link. In short, you have to apply filter for inner entities as well.
I try to implement 1..0 relations with Entity Framework 6. I use instance associsaltion. I try to repeat the examples from web and forums but somehow it doesn't work for me. Please, help.
Entities:
public class CustomerWithFk : Item // Item contains Id
{
public string Name { get; protected set; }
public virtual City City { get; set; } // relation property. Can be 1 or 0
public virtual Product Product { get; set; }
public decimal Money { get; protected set; }
}
public class City : Item
{
public string Name { get; protected set; }
}
Mappings:
public CityMap()
{
ToTable("Cities");
HasKey(c => c.Id);
}
public CustomerFkAssosiationMap()
{
ToTable("Customers");
HasKey(c => c.Id);
HasRequired(g => g.City)
.WithRequiredDependent();
HasRequired(g => g.Product)
.WithRequiredDependent()
.Map(x => x.MapKey("ProductId"));
}
Database tables:
SQL Profiler gives me enxt SQL request:
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Money] AS [Money],
[Extent1].[CityId] AS [CityId],
[Extent1].[ProductId] AS [ProductId]
FROM [dbo].[Customers] AS [Extent1]
So, I don't see any joins here to load data from Cities or Products.
And the result is Null:
I tried different mapping options, like: HasOptional, WithRequiredPrincipal, tried to add Customers proeprty to City (while it's incorrect and City doesn't have to know something about customers)
Nothing helps. The assosiated entities are always null.
Where am I wrong?
The problem is that you are not including the related objects. Try something like this using Include:
var list = context.CustomerWithFk
.Include("City")
.Include("Product");
That tells Entity Framework that you want to pull back the customer along with the city and product. Here is some further reading if you are interested: http://msdn.microsoft.com/en-us/data/jj574232.aspx.
EDIT: You could also enable lazy loading (based on your comment I believe it is what you are after) by adding this to your context:
context.ContextOptions.LazyLoadingEnabled = true;
Read more about lazy loading here: http://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx.
Newbie Alert
I am trying to check if an entity exists in the database, if it does i want to update it else create a new entity. But CreateCriteria use always returns an entity with no id? Any ideas why? I am using fluent nhibernate for manual mapping i.e use of ClassMap;
Base class -hold only the Id public property
public abstract class EntityBase : IEquatable
{
public virtual int Id { get; set; }
public virtual bool Equals(EntityBase obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (GetType() != obj.GetType()) return false;
return obj.Id == Id;
}
}
MAPPING;
public class ProjectNameMap: ClassMap<ProjectName>
{
public ProjectNameMap()
{
Table("dbo.TABLENAME");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.PROJECT_NAME).Not.Nullable();
Map(x => x.PROJECT_DESCRIPTION);
}
}
Getting back the entity;
public static T GetEntityByRestrictions<T>(String propertyName, String propertyValue)
where T:EntityBase
{
using (var session = SessionManager.CreateSessionFactory().OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var entity= session.CreateCriteria<T>(propertyValue)
.Add(Restrictions.Eq(propertyName, propertyValue))
.UniqueResult<T>();
return entity;
}
}
}
}
Two silly steps i tried (dont laugh)
1. Silly step i tried was to manually set the Id =52 matching existing database entry and i keep on getting a primary key violation on project name as the database expects unique project name.
More silly steps (i can hear laughter) modified mapping file to include Map(x=>x.Id).Update().Insert() and this lead to INSERT_IDENTITY set to OFF (or somethin).
So whats the best way to get an entity with Id and update afterwards,is there something wrong with my CreateCriteria?
I believe calling the Merge method on the session will do exactly what you want.
Just put in the entity you want to update/insert as an argument and it will update the properties if the entity already exists or persist the new instance if it does not. This way you don't need the CreateCriteria and your solution will be a lot simpler.
OK, first my simple Domain Model is 2 classes with a one-to-many relationship, a simple Parent -> child relationship. A 'Tweet' has one or more 'Votes', but each Vote belongs to just one Tweets etc.
public class Tweet
{
public virtual long Id { get; set; }
public virtual string Username { get; set; }
public virtual string Message { get; set; }
public virtual ISet<Vote> Votes { get; set; }
}
public class Vote
{
public virtual long Id { get; set; }
public virtual long TwitterUserId { get; set; }
public virtual DateTime VotedDate { get; set; }
public virtual Tweet Tweet { get; set; }
}
I'm trying to write a query in either HQL, ICriteria or NHibernate LINQ, that selects all Tweets, but also add two columns that selects:
A count of the number of votes, and...
Whether a particular user has voted for that tweet, based on a particular TwitterUserId
With the two extra columns, I'm not expecting to get Tweet domain object back, and would probably need to run a Report query, that's OK. But I'm struggling to figure out how to write this query.
I know how to write this as a Stored Procedure, or using LINQ 2 SQL. If it helps I will express this as a LINQ to SQL query.
long userId = 123;
var tweets = from t in dataContext.Tweets
where t.Application == app
orderby t.PostedDate desc
select new TweetReport()
{
Id = t.Id,
Username = t.Username,
Message = t.Message,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
};
I know this will work in LINQ 2 SQL, and generate reasonably efficient T-SQL, but I can't figure out how to write this in NHibernate.
Update: I tried running the above LINQ query in NHibernate by using the NHibernate LINQ provider built for NHibernate v2, eg:
var tweets = from t in Session.Linq<Tweet>()
where (snip)
But it didn't work. Has LINQ support in Nhibernate 3.0 improved? I'm a bit reluctant to use version 3.0 because it's still alpha, but if this will work, then I might give it a go.
Update 2: Thanks to Diego Mijelshon's suggestion, I upgraded to NHibernate 3.0 alpha 2 and wrote the query in LINQ:
var tweets = from t in Session.Query<Tweet>()
where t.App == app
orderby t.PostedDate descending
select t;
int totalRecords = tweets.Count();
var pagedTweets = (from t in tweets
select new TweetReport()
{
Id = t.Id,
TwitterId = t.TweetId,
Username = t.Username,
ProfileImageUrl = t.ImageUrl,
Message = t.Message,
DatePosted = t.PostedDate,
DeviceName = t.Device.Name,
DeviceUrl = t.Device.Url,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
})
.Skip(startIndex)
.Take(recordsPerPage)
.ToList();
return new PagedList<TweetReport>(pagedTweets,
recordsPerPage, pageNumber, totalRecords);
It's exactly the same with NHibernate 3; just replace dataContext.Tweets with session.Query<Tweet>.
Alternatively, you can create a context class that exposes session.Query<Tweet> as an IQueryable<Tweet> Tweets property, then the code would be 100% unchanged.
I have a database schema that stores one "Page" with many "Revisions". Like a simple wiki.
90% of the time when I load a page, I am just interested in the latest revision. However, sometimes I want all revisions.
With NHibernate I can map the Page to the Revisions, and tell it to lazy-load. However, when I access the latest revision, it will load all other revisions - a big waste of I/O.
My page class currently resembles:
public class Page
{
public Page()
{
Revisions = new HashedSet<Revision>();
}
public virtual ISet<Revision> Revisions { get; private set; }
public virtual Revision LatestRevision
{
get { return Revisions.OrderByDescending(x => x.Revised).FirstOrDefault(); }
}
public virtual Revision Revise()
{
var revision = new Revision();
// ...
revision.Entry = this;
revision.Revised = DateTime.UtcNow;
Revisions.Add(revision);
return revision;
}
}
How would I model this such that the LatestRevision is automatically loaded when the Page is loaded, but the other revisions are lazy-loaded if, for instance, I attempted to iterate them?
I would particularly like a solution that works with LINQ to NHibernate, but using ICriteria (or even SQL if I have to) is good enough.
I'm tackling a similar problem as well.
What about mapping exactly as you have it there. The LatestRevision property could be mapped as a one-to-one mapping to the revisions table and the revisions would be as you've already got it. You would have to have a setter (probably make it private) and manage the relationship in the revise method.
One problem would be that the the LatestRevision would still be in the set of revisions.
I've also come across a post by Ayende which uses the formula attribute for the property, I've never used it but looks like it might fit the bill.
You could use a derived property in your mapping file (rather than performing the logic in the property). It might look something like this:
<property name="LatestRevision"
forumla="select top r.f1, r.f2, r.etc from Revisions order by revised desc"
type="Revision" />
For more info on this approach search for 'nhibernate derived properties'.
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html_single/
Add a LatestRevision column (maintain it) and map to that.
It will save you a lot of headaches.
I ended up going with the solution from here:
Partially Populate Child Collection with NHibernate
My page now has these properties:
public virtual Revision CurrentRevision
{
get
{
return _revision ?? Revisions.OrderByDescending(x => x.Revised).FirstOrDefault();
}
set { _revision = value; }
}
public virtual ISet<Revision> Revisions { get; private set; }
The loading code is:
public Page GetPage(string name)
{
var entryHash = (Hashtable)_session.CreateCriteria<Page>("page")
.Add(Restrictions.Eq("page.Name", name))
.CreateCriteria("Revisions", "rev")
.AddOrder(Order.Desc("rev.Revised"))
.SetMaxResults(1)
.SetResultTransformer(Transformers.AliasToEntityMap)
.UniqueResult();
var page = (Page)entryHash["page"];
page.LatestRevision = (Revision)entryHash["rev"];
return page;
}
NHProf shows this as the only query being executed now, which is perfect:
SELECT top 1 this_.Id as Id3_1_,
this_.Name as Name3_1_,
this_.Title as Title3_1_,
rev1_.Id as Id0_0_,
rev1_.Body as Body0_0_,
rev1_.Revised as Revised0_0_,
....
FROM [Page] this_
inner join [Revision] rev1_
on this_.Id = rev1_.PageId
WHERE this_.Name = 'foo' /* #p0 */
ORDER BY rev1_.Revised desc
What the problem to have LatestRevision property and corresponding column in Page table?
public class Page
{
public Page()
{
Revisions = new HashedSet<Revision>();
}
public virtual ISet<Revision> Revisions { get; private set; } // lazy="true"
public virtual Revision LatestRevision { get; private set; } // lazy="false"
public virtual Revision Revise()
{
var revision = new Revision();
// ...
revision.Entry = this;
revision.Revised = DateTime.UtcNow;
Revisions.Add(revision);
LatestRevision = revision; // <- there you have latest revision
return revision;
}
}