How to efficiently filter a collection of child objects by a property with NHibernate - nhibernate

I've been trying unsuccessfully to filter a collection of child objects for a few hours how and have finally thrown my hands up! I'm new to NHibernate and was hoping for a couple of pointers on this. I've tried various ICriteria etc. with no luck. I'm just not getting it.
I have a parent object 'Post' with a collection of child objects 'Comment'. The collection is mapped as a set with inverse on the Comment side.
What I am trying to do is to return only comments with a status enum value of 'Comment.Approved'
The relevant portions of the entity classes are as follows:
public class Post
{
public virtual Guid Id { get; protected set; }
private ICollection<Comment> _comments;
public virtual ICollection<Comment> Comments
{
get { return _comments; }
protected set { _comments = value; }
}
}
public class Comment
{
public virtual Guid Id { get; protected set; }
public virtual Post Post { get; set; }
public virtual CommentStatus Status { get; set; }
}
My retrieval code looks like this at the moment:
var Id = __SomeGuidHere__;
var post = _session
.CreateCriteria<Post>()
.Add(Restrictions.Eq("Id", Id))
.UniqueResult<Post>();
var comments = _session.CreateFilter(post.Comments, "where Status = :status").SetParameter("status", CommentStatus.Approved).List<Comment>();
While this works the SQL doesn't appear to be very efficient, I expected to be able to translate the following SQL into something similar in HQL or an ICriteria of some sort:
SELECT * FROM posts p LEFT JOIN comments c ON p.PostId = c.PostId AND c.Status = 0 WHERE p.PostId = '66a2bf13-1330-4414-ac8a-9d9b00ea0705';
I've had a look at the various answers related to this type of query here and none of them seem to address this specific scenario.
There's probably something very simple I'm missing here but I'm too tired now to see it. Here's hoping someone better with NHibernate can point me in the right direction.
Thanks for your time.
Edit: Still struggling with this, some of the answers here are good in that I'm starting to think that my post entity needs to be re-thought to perform the filtration itself, or that I should implement a ViewModel to filter the comments I want. The question still remains however, even if only from an academic perspective.
I've updated the selection to HQL and tried:
var post = _session
.CreateQuery("select p from Post as p left join fetch p.Comments as c where p.Id = :id and c.Id in (select ac from p.Comments ac where ac.Status = :status)")
.SetParameter("id", Id)
.SetParameter("status", CommentStatus.Approved)
.UniqueResult<Post>();
This works as long as a post has an approved comment, otherwise I get no post due to the SQL generated using 'AND' in the where clause.
Anyone? I'm stumped now!
Update: Thanks to all who have replied, it has been useful and has forced me to re-evaluate portions of my model. Since the most frequent use of comments as children of a post is in the viewing of a post, only the approved comments should be viewable in this scenario. In most other scenarios that I can think of comments would be accessed directly and filtered by status which is of course straight forward.
I have updated my mappings to filter all post > comment loading to only load approved posts as follows (in FluentNHibernate):
HasMany(x => x.Comments).Where(x => x.Status == CommentStatus.Approved)
.AsSet()
.Inverse()
.KeyColumn("PostId")
.ForeignKeyConstraintName("PostComments")
.OrderBy("CreatedOn")
.Cascade.AllDeleteOrphan();
I wish I could mark all as the answer since all contributed to me working this thing out, but Peter was the first to point out that I may be thinking about the model incorrectly.
Thanks to all.

According to me the SQL is perfect for what you describe.
The database has its own optimizer and will know what to do to get your data efficiently delivered to your doorstep.

I think this would work fine:
var comments =
_session.CreateCriteria<Comment>
.CreateAlias("Post", "post")
.Add (Restriction.Eq("Status", status))
.Add (Restriction.Eq("post.Id", Id)).List();
I am not shure if you need the .CreateAlias part (if you don't need it, maybe Post.Id would work too - but I am not sure).

I would solve this using an extension method for IEnumerable<Comment>:
public static IEnumerable<Comment> FilterByStatus(this IEnumerable<Comment> comments, CommentStatus status)
{
return comments.Where(x => x.Status == status);
}
Let NHibernate return the entire collection then filter it as needed.

Related

Creating a new Content Item in the migration with Orchard CMS

Here's my migration code:
public Migrations(IRepository<ProductPartRecord> productPartRepository, IRepository<CategoryPartRecord> categoryPartRepository)
{
_productPartRepository = productPartRepository;
_categoryPartRepository = categoryPartRepository;
}
public int Create() {
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("AutoroutePart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("AutoroutePart", partBuilder => partBuilder
.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Category Title', Pattern: 'category/{Content.Slug}', Description: 'category/category-title'}]")));
SchemaBuilder.CreateTable("CategoryPartRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<string>("Description")
.Column<string>("Image")
);
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CategoryPart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.Creatable()
.Draftable());
return 1;
}
public int UpdateFrom1() {
_categoryPartRepository.Create(new CategoryPartRecord { Name = "Category1", Description = "Description1", Image = "Image1" });
return 2;
}
UpdateFrom1 obviously attempts to insert a dummy record, but this causes nHibernate to throw this exception:
"attempted to assign id from null one-to-one property: ContentItemRecord"
The Part Record looks like:
public class CategoryPartRecord : ContentPartRecord {
public CategoryPartRecord()
{
CategoryProducts = new List<CategoryProductRecord>();
}
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Image { get; set; }
public virtual IList<CategoryProductRecord> CategoryProducts { get; set; }
}
Any clues as to where I'm going wrong here? Google produced nothing.
Okay, so you are creating a contentpartrecord, not a content item there. What you want is something more along the lines of:
var item = _orchardServices.ContentManager.New("Category").As<CategoryPart>();
item.Name = "Bobs Item"; // Something like that...
item.ContentItem.As<TitlePart>().Title = "Yay a title"; // This syntax may be wrong, I'm very tired
_orchardServices.ContentManager.Create(item);
_orchardServices.ContentManager.Publish(item.ContentItem);
I think that is how you would do it. Maybe you would want to look into creating content items using the import/export module, that is the more common and safe way to do it.
Not sure if the answer from Hazza works. Haven't tried that.
I usually just do this: (But not sure if it's an inferior approach in some way)
var item = _orchardServices.ContentManager.New("Category");
var cpart = item.As<CategoryPart>();
var tpart = item.As<TitlePart>();
cpart.Name = "SomeName";
tpart.Title = "SomeTitle";
_orchardServices.ContentManager.Create(item);
But to address the comment by Lawrence Johnson:
Category in this case is the content item. He is creating a new Category content item, and then extracting the corresponding CategoryPart from it.
If you are getting null when trying to extract the part you're probably missing something.
In order for this to work you need to implement the CategoryPart, CategoryPartRecord, CategoryPartHandler and CategoryPartDriver. (And of course make sure to attach your CategoryPart to you Category content item. Not certain if placement.info is required, but would add it for consistency anyway.)
You can't leave any of these out if you plan to use a Part attached to a content item.
I'm not sure if/how you can create a Part with no content item, but you can create a Record with no part and no content item (Just make sure you don't inherit ContentPartRecord in your record object). If you simply want to add a record with no part or content item, then the code in UpdateFrom1 used by Ben Power would work for creating a record. (But migration part would have to be changed, taking out the content item and part, and manually setting the Id to be a primary key for the record)

LightSwitch - bulk-loading all requests into one using a domain service

I need to group some data from a SQL Server database and since LightSwitch doesn't support that out-of-the-box I use a Domain Service according to Eric Erhardt's guide.
However my table contains several foreign keys and of course I want the correct related data to be shown in the table (just doing like in the guide will only make the key values show). I solved this by adding a Relationship to my newly created Entity like this:
And my Domain Service class looks like this:
public class AzureDbTestReportData : DomainService
{
private CountryLawDataDataObjectContext context;
public CountryLawDataDataObjectContext Context
{
get
{
if (this.context == null)
{
EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
builder.Metadata =
"res://*/CountryLawDataData.csdl|res://*/CountryLawDataData.ssdl|res://*/CountryLawDataData.msl";
builder.Provider = "System.Data.SqlClient";
builder.ProviderConnectionString =
WebConfigurationManager.ConnectionStrings["CountryLawDataData"].ConnectionString;
this.context = new CountryLawDataDataObjectContext(builder.ConnectionString);
}
return this.context;
}
}
/// <summary>
/// Override the Count method in order for paging to work correctly
/// </summary>
protected override int Count<T>(IQueryable<T> query)
{
return query.Count();
}
[Query(IsDefault = true)]
public IQueryable<RuleEntryTest> GetRuleEntryTest()
{
return this.Context.RuleEntries
.Select(g =>
new RuleEntryTest()
{
Id = g.Id,
Country = g.Country,
BaseField = g.BaseField
});
}
}
public class RuleEntryTest
{
[Key]
public int Id { get; set; }
public string Country { get; set; }
public int BaseField { get; set; }
}
}
It works and all that, both the Country name and the Basefield loads with Autocomplete-boxes as it should, but it takes VERY long time. With two columns it takes 5-10 seconds to load one page.. and I have 10 more columns I haven't implemented yet.
The reason it takes so long time is because each related data (each Country and BaseField) requires one request. Loading a page looks like this in Fiddler:
This isn't acceptable at all, it should be a way of combining all those calls into one, just as it does when loading the same table without going through the Domain Service.
So.. that was a lot explaining, my question is: Is there any way I can make all related data load at once or improve the performance by any other way? It should not take 10+ seconds to load a screen.
Thanks for any help or input!s
My RIA Service queries are extremely fast, compared to not using them, even when I'm doing aggregation. It might be the fact that you're using "virtual relationships" (which you can tell by the dotted lines between the tables), that you've created using your RuleEntryTest entity.
Why is your original RuleEntry entity not related to both Country & BaseUnit in LightSwitch BEFORE you start creating your RIA entity?
I haven't used Fiddler to see what's happening, but I'd try creating "real" relationships, instead of "virtual" ones, & see if that helps your RIA entity's performance.

Does Fluent Nhibernate Lazy loads IList from Criteria

I want to make a kind of "news feed" in the application of my company.
In the scenario, User actions will generate "Activity" of different kinds, and other users will see in their "news feed".
However, an "Activity" is not related to all users, and to determine the relation, we have a complex piece of code.
Here is my Activity class
public class Activity: IActivity
{
public virtual int Id { get; set; }
public virtual ActivityType Type { get; set; }
public virtual User User { get; set; }
public virtual bool IsVisibleToUser(User userLook)
{
// Complex business calculation etc.
return true;
}
}
I want to get latest 10 news that is visible to User. But since the Activity table will be quite huge, and performance is an issue, I want to do the best practice about it.
What i am about to do, is get 25 last Activity, and check if we fill the list to show to user. For example, if only 5 Activity is visible to user, i will get another 25 Activities and so on.
IList<Activity> resultList = session.CreateCriteria(typeof(Activity))
.SetMaxResults(25)
.AddOrder(Order.Desc("Id"))
.List<Activity>();
I want to learn, if I get the whole list ordered by Id, and check one by one if it is visible to User, would NHibernate only loads the objects that i use for me or not?
IList<Activity> resultList = session.CreateCriteria(typeof(Activity))
.AddOrder(Order.Desc("Id"))
.List<Activity>();
int count = 0;
foreach( Activity act in resultList){
if (act.IsVisible(CurrentUser)){
count++;
// Do something with act
if (count == 10)
break;
}
}
EDIT:
Here is ActivityMapping for Activity model.
public class ActivityMap : ClassMap<Activity>
{
public ActivityMap()
{
Id(x => x.Id);
Map(x => x.Type).CustomType(typeof(Int32));
References(x => x.User).Nullable();
}
}
If your question is about how the generated SQL would look like, my guess would be :
SELECT
this_.Id as Id0_0_,
this_.ActivityTypeas ActivityType_0_0_,
--Other fields
FROM dbo.ActivityType this_
WHERE
--condition
ORDER BY
--condition
Since you have mentioned that the Activity count is huge, you can make use of
ICriteria's SetFirstResult and SetMaxResult.
SetFirstResult(int) indicates the index of the first item that you wish to obtain and SetMaxResult(int) indicates the number of rows you wish to get, 25 in your case.
The ToList would load all the records in memory at once.
[UPDATE] If you need the records to be returned one by one, make use of Enumerable() -
If you expect your query to return a very large number of objects, but you don't expect to use them all, you might get better performance from the Enumerable() methods, which return a System.Collections.IEnumerable. The iterator will load objects on demand, using the identifiers returned by an initial SQL query (n+1 selects total).
Source - Link
No, the List() method pulls everything into memory at once.

How to update a specific fields of class in Fluent NHibernate with out updating the whole object? [duplicate]

This question already has answers here:
NHibernate update on single property updates all properties in sql
(2 answers)
Closed 5 years ago.
I am using Fluent NHibernate to do my NHibernate mappings, but now I have come to a problem that I am not sure how to solve. A simplified version of the problem follows.
I have a user class:
public class User {
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
This is the associated Fluent NHibernate Class Map
public class UserMap : ClassMap<User> {
public UserMap() {
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
}
}
I have two web forms. One form allows me to change the users first name, and the second form allows me to change the users last name. What I am trying to achieve is a simple SQL statement like this:
For the first form:
UPDATE [users] SET firstname='new first name' WHERE id=1
For the second form:
UPDATE [users] SET lastname='new last name' WHERE id=1
Currently NHibernate performs the following SQL on my database:
UPDATE [users] SET firstname=null, lastname='new last name' WHERE id=1
The problem in the real world application, is that there are too many properties to update on some big objects (as well as access restrictions), and it seems pointless to update the whole object, when all I want / am allowed to do is update a single property.
I am hoping that someone can provide some advice as to how I can realise this, or point me in the right direction to solve this.
Hibernate's doing the right thing, but your problem indicates that your schema needs some normalization.
Ok, that works, Thanks for the help and tips Queen3!
here is how I sovled it:
using (var sf = Repository.CreateSessionFactory()) {
using (var s = sf.OpenSession()) {
using (var t = session.BeginTransaction()) {
var existingUser = s.Get<User>(editedUser.Id);
existingUser.LastName = editedUser.LastName;
s.SaveOrUpdate(existingUser);
t.Commit();
}
}
}
Although this does work, it requires that I retrieve the User from the database first and work within the same session. The good thing is that the sql statement that is generated just updates the dirty LastName field. :-)
I am unable to get it to work with a detached instance of the user, this is similar to how I was doing it before, which resulted in every field of the user being updated.
using (var sf = Repository.CreateSessionFactory()) {
using (var s = sf.OpenSession()) {
using (var t = session.BeginTransaction()) {
s.SaveOrUpdate(editedUser);
t.Commit();
}
}
}

NHibernate: Creating a criteria which applies for all queries on a table

Using Castle ActiveRecord / NHibernate: Is there a way you can force an ICriterion on all queries on a table?
For example, a good amount of of my tables have a "UserId" column. I might want to ensure that I am always selecting rows for the logged in user. I can easily create an ICriterion object, but I am forced to supply it for different methods: FindAll(), FindFirst(), FindLast() etc.
Is there a way to force a WHERE clause on all queries to a Castle ActiveRecord?
I finally found a great solution (using filters). Since Castle AR does not have any native API for mapping to NHibernate filters, this part was pretty much undocumented. So here goes.
This example filter, will make sure you will never get news more than a year old, no matter what kind of query you use on the ActiveRecord. You can probably think of more practical applications for this.
First, create an ActiveRecord "News".
Use the following code before you initialize ActiveRecordStarter.
ActiveRecordStarter.MappingRegisteredInConfiguration += MappingRegisteredInConfiguration;
Castle.ActiveRecord.Framework.InterceptorFactory.Create = () => { return new EnableFiltersInterceptor(); };
Then, add the missing function and class:
void MappingRegisteredInConfiguration(Castle.ActiveRecord.Framework.ISessionFactoryHolder holder)
{
var cfg = holder.GetConfiguration(typeof (ActiveRecordBase));
var typeParameters = new Dictionary<string, IType>
{
{"AsOfDate", NHibernateUtil.DateTime}
};
cfg.AddFilterDefinition(new FilterDefinition("Latest", "", typeParameters));
var mappings = cfg.CreateMappings(Dialect.GetDialect(cfg.Properties));
var newsMapping = cfg.GetClassMapping(typeof (News));
newsMapping.AddFilter("Latest", ":AsOfDate <= Date");
}
public class EnableFiltersInterceptor : EmptyInterceptor
{
public override void SetSession(ISession session)
{
session.EnableFilter("Latest").SetParameter("AsOfDate", DateTime.Now.AddYears(-1));
}
}
And voila! Queries on News, e.g. FindAll(), DeleteAll(), FindOne(), Exists(), etc. will never touch entries more than a year old.
The closest thing would be using filters. See http://ayende.com/Blog/archive/2009/05/04/nhibernate-filters.aspx