(NHibernate) how to filter a collection on fetch - nhibernate

Requirement:
I'm trying to query a list of customers.
Each customer can contain a list of contacts.
Only active contacts should be returned.
Code:
Session.QueryOver<Customer>()
.Fetch(x => x.Contacts.Where(c => c.Active))
.Eager
.TransformUsing(new DistinctRootEntityResultTransformer())
.Future()
.AsQueryable();
Error:
Unrecognised method call in expression x.Contacts.Where(c => c.Active)
So, how can I filter only active contacts?

You don't because doing so would create a mismatch between the database and the domain model. If you were able to do this, the inactive contacts would be deleted when the session is flushed. There are two good options:
Create an extension method on IEnumerable<Contact> that returns active contacts. This allows you to easily filter any set of Contacts to display only the active ones.
Create a view model containing only the data you want to display.
NHibernate filters may also be an option but I have no experience with them. I favor using extension methods.

Related

Property [participants_count] does not exist but exists and can be dumped

I have 2 tables, evenements and participants, represented by 2 models Evenement and Participant.
Those entities are belongsToMany related, so I have a third table evenement_participant following Laravel's naming conventions, and inside are foreign evenement_id and participant_id columns.
I'm able to retrieve the relationship and I can
dd($evenement->participants)
which gives me a collection of participants.
In my controller, I have this db call:
$evenements = Evenement::withCount(['participants' => function($query) {
$query->where('is_active', 1);
}])
This withCount generates a participants_count attribute for each evenement.
In my blade view, there is a for-each loop on the evenements collection, and somewhere I do this:
$evenement->participants_count
and I face this error:
Property [participants_count] does not exist on this collection
instance.
However, if instead I do the following in the same blade view
#dd($evenement->participants_count)
it dumps me the count.
I dropped all the evenements to keep just one for testing, and I still have the same error.
Sorry, made a typo in a condition inside my blade loop

Podio API: how to search in all fields except item comments?

I need to search for a text within a particular workspace. I need all items and fields, except comments.
I'm using php-wrapper for Podio API and Search in space function:
$attributes = array(
"query" => $query,
"ref_type" => "item", // I need just items, not tasks, statuses etc.
"search_fields" => "title"
);
$items = PodioSearchResult::space( $space_id, $attributes );
If search_fields parameter will be removed, it will search not only in titles, but in all fields. However, it will also search in comments left for each item and return that items as a result. But I need just results based on fileds values.
Of course, it is possible to list all the fields needed in search_fields. But there is a dozen of apps with a dozen of different fields each in that space. Moreover, fields could be added, edited or removed by workspace users. So it looks like a very rough and hard-coded solutiuon to list all the fields.
Is there another way to avoid comments in search results?
Podio doesn't have specific method to avoid only comments. But instead of hardcoding all the fields, you can query dynamically "Get app values" call and use the result in "search_fields".

Create a ActiveDataProvider based on ActiveRecord Relation in Yii2

I have a many-to-many relation setup using a junction table in MySQL. Table Article is related to Activity via table Article_Activity.
In model Article I have a relation setup like this
public function getActivities()
{
return $this->hasMany(Activity::className(), ['id' => 'activity_id'])
->viaTable('article_activity', ['article_id' => 'id']);
}
When rendering a view for one Article I would like to display a GridView of all Activities related to that Article.
The way most people seem to this is to create a ActiveDataProvider and insert a query into it that fetches related data but that feel a bit redundant since I have the relation setup in the model and there should be a way to get a dataprovider from that.
My question is: Is there a way to get a yii\data\ActiveDataProvider or yii\db\Query based on a instantiated models relation that can be used to display all related records in a GridView?
You can actually call it like this:
$dataProvider = new ActiveDataProvider([
'query' => $article->getActivities(),
]);
If you call the get method directly you get a yii\db\ActiveQueryInterface which is what you need to provide as query to the ActiveDataProvider.
When you call the activities attribute like $article->activities the ActiveQueryInterface is executed and you get the records from the query results.
Based on you Article model you have the activities relation that get multiple Activity related to you Article
a normal dataProvider like
$dataProvider = new ActiveDataProvider([
'query' => Article::find()->joinWith(['activities'])->
where(['your_column'=> $your_value]),
]);
return activeRecord whit also the related activities
you can refer to the related models inside the dataProvider
$models = $dataProvider->models; // this is a collection of model related to the dataProvider query
// so the firts model is $model=models[0]
then for each of this you can obtain acyivities
$activities = $model->activities;
or value
$my_activity_field = $model->activities->my_activity_field

Eloquent: Get pages based on user role

I have a User, Role & Page setup, all with many-to-many relationships and the pivot tables setup in the usual fashion (role_user, page_role), along with the eloquent methods to attach a model to the pivot tables.
My idea is to allow a user to have many roles, and a page can be accessed by many roles.
However now I'd like to return a collection where I have my users details and then the pages they're allowed to access.
The closest I have got is:
return User::find( Auth::user()->id )->with('roles.pages')->first()->roles;
Now this returns each role the user has, and each page that the role can access. Which is correct, however I have duplication on the pages part.
How would I go about getting only a list of pages the user is able to access with no duplication?
Cheers
Read that answer to get you on the track: HasManyThrough with one-to-many relationship
Only for your setup you need to adjust the query - join 2 pivot tables (and make sure they represent real data, ie no rows referencing non-existing models):
// User model
// accessor so you can access it like any relation: $user->pages;
public function getPagesAttribute()
{
if ( ! array_key_exists('pages', $this->relations)) $this->loadPages();
return $this->getRelation('pages');
}
// here you load the pages and set the collection as a relation
protected function loadPages()
{
$pages = Page::join('page_role as pr', 'pr.page_id', '=', 'pages.id')
->join('role_user as ru', 'ru.role_id', '=', 'pr.role_id')
->where('ru.user_id', $this->id)
->distinct()
->get(['pages.*', 'user_id']);
$hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(Page::query(), $this, 'user_id', 'id');
$hasMany->matchMany(array($this), $pages, 'pages');
return $this;
}
One more thing: I hardcoded tables and columns names for sake of simplicity, but in real life I suggest you rely on the relationships and their getters, like: $relation->getTable(), $relation->getForeignKey() etc.
Now suggestion about your code:
return User::find( // 2. query to get the same user
Auth::user()->id // 1. query to get the user and his id
)->with('roles.pages')
->first() // 3. query to get ANOTHER user (or the same, luckily..)
->roles;
Use Auth::id() instead of Auth::user()->id (for Laravel ver 4.1.25+) to avoid redundant query
find() and first() are methods that execute the query, so you just returned the user with id = Auth::user()->id and moment later you fetch another one, who comes first() from the users table..
You don't need to use User::whatever for authenticated user, use Auth::user() instead.
So the code with suggested solution would look like this:
Auth::user()->pages; // collection of Page models with unique entries

How do I use Fluent Nhibernate many-to-many for optimal performance?

I have a product table that has a many-to-many relation to itself (using a two-column many-to-many table) and I have set it up in Fluent NHibernate with the following code:
public class ProductConfiguration : ClassMap<Product>
{
public ProductConfiguration()
{
Table("Product");
Id(p => p.Id).GeneratedBy.Guid();
Map(p => p.Name).Not.Nullable().Length(254);
Map(p => p.Description).Not.Nullable().Length(1000);
Map(p => p.CreatedAt).Not.Nullable();
HasManyToMany(p => p.CrossSell)
.Table("ProductCrossSell")
.ParentKeyColumn("Id")
.ChildKeyColumn("ProductId");
}
}
My MVC application has two pages that uses this setup:
Index - Uses a generic repository GetAll method to display all products.
Detail - Uses a generic repository GetById method to display one product and any related cross sell products setup in the many-to-many realation.
It looks like NHibernate is set to LazyLoad the many-to-many by default so when I fire up the application and watch it in profiler I can see that it does LazyLoad the many-to-many with the following alert "Use of implicit transactions is discouraged".
How do I get rid of this alert? I couldn't find any information on how to wrap a LazyLoad inside a transaction to get rid the alert. Is it even possible?
Is there a way to not lazyload this by telling NHibernate that whenever I ask for GetById make sure to join the tables a get everything in one query? I tried using .Fetch.Join() in the many-to-many mapping but that also affected my GetAll query which now displays a joined result set as well which is incorrect.
What is the best apprach for this kind of simple scenario?
Thanks
The way to get rid of the warning is to access the object graph and fully populate the UI elements inside a single transaction.
Not by configuration. You can create an HQL query that eager fetches the association and use that query for a specific view. I would stick with lazy loading and not make that optimization unless needed. The HQL would be:
return session.CreateQuery("from ProductionConfiguration pc join fetch pc.CrossSell where pc.Id = ?")
.SetGuid(0, id)
.List<ProductConfiguration>();
All collections are lazily loaded in NHibernate by default.
You must be triggering loading with a call of some kind (maybe even with the debugger watches)