NHibernate QueryOver - Doing Fetches and OrderBy -> What Syntax to use? - asp.net

All,
I have a query as such:
_AddOrderBy(sortOptions, query)
.Fetch(x => x.ImageType).Eager
.Fetch(x => x.User).Eager
.Fetch(x => x.Partner).Eager
.Inner.JoinAlias(x => x.Partner, () => p).Fetch(x => x.Company).Eager
.Skip(startIndex)
.Take(pageSize)
.List<ImageRequest>();
In the above QueryOver I call _AddOrderBy() method which adds an order by clause. The challenge I face is how do I create an "order by" that references a property (ordering by "CompanyName") that lies within the following association path without conflicting with my Fetch()/Inner joins:
ImageRequest.Partner.Company.CompanyName
Inside my _AddOrderBy() I have this:
Partner p = null;
Company comp = null;
order = query.Inner.JoinAlias(x => x.Partner, () => p)
.Inner.JoinAlias(x => x.Company, () => comp)
.OrderBy(x => comp.CompanyName);
But this gives me a run time exception stating that I have duplicate key (referring to Partner) in the criteria. I can see that this is conflicting with my eager fetching.
My questions is:
How do I add an "order by" so it works with my Fetching.

The beauty of using an Alias in QueryOver is that you don't have to use Fetch or JoinAlias in your _AddOrderBy() method again if the Join happens in the query already. You only need to declare the Alias with the same name.
Therefore your _AddOrderBy() can just look like this:
Partner p = null;
Company comp = null;
order = query
.Inner.JoinAlias(x => p.Company, () => comp) // you can use p here if it was used in the query before
.OrderBy(x => comp.CompanyName);
The reason this works is this: If you put the whole code into one method it will obviously work. Splitting it into two methods still works, because Partner p is not a reference to an object in memory but an Alias that is simply translated into a string for the SQL query.

Related

Use Select instead of Include

so I have this big table that I want to query and it's so slow, I got a tip that using select is more efficient than include. so how can I convert include to select, in this big query.
ProductEntity? i = await _context.Set<ProductEntity>()
.Include(i => i.Media)
.Include(i => i.Categories)
.Include(i => i.Locations)
.Include(i => i.Reports)
.Include(i => i.Comments)!.ThenInclude(x => x.LikeComments)
.Include(i => i.Bookmarks)
.Include(i => i.Votes)
.Include(i => i.User)!.ThenInclude(x => x.Media)
.Include(i => i.User)!.ThenInclude(x => x.Categories)
.Include(i => i.Forms)!.ThenInclude(x => x.FormField)
.Include(i => i.Teams)!.ThenInclude(x => x.User)!.ThenInclude(x => x.Media)
.Include(i => i.VoteFields)!.ThenInclude(x => x.Votes)
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Id == id && i.DeletedAt == null);
return new GenericResponse<ProductEntity>(_mapper.Map<ProductEntity>(i));
and this is getById method, which just takes one row, I have a getAll that returns a list of products just like this.
The most performant code is always the code that does the least amount of work.
It's possible that what was meant by the 'tip' that "using select is more efficient" may have more to do with answering the question: "do you really, really, really need all that data to be returned?".
Consider, for every .Include you have, EF will be making it's own query to the database to return whatever records are linked by the foreign keys. It's actually pretty efficient on doing that either on the DB side, or in memory if it already has the connected records in memory. (depending on your caching configuration) However, it's going to return the entire Entity referenced. Which may be fine. But, what if each of those 11 sub-entities plus the 7 sub-sub-entities that you have connecting there are each rather large. Do you really, really, really need to return all of that data? Or could you return smaller objects using something like:
var mySmallerObject = await _context.Set<ProductEntity>()
.Include(i => i.Media)
.Include(i => i.Categories)
. . .
.Select(_ => new SomeMuchSmallerObject {
SomeFieldOnProduct = _.SomeFieldOnProduct,
TheOnlyThingINeedFromMedia = _.Media.TheOnlyThingINeedFromMedia,
TheOnlyCategoryFieldINeed = _.Categories.TheOnlyCategoryFieldINeed,
. . . // etc
})
UPDATED
Note: You will still need to either keep all those .Include()s to let EF implicitly build the joins, or else convert them to explicit .Join() statements. By making a smaller object that you use in the .Select() statement, EF will translate this request into a much more optimized query to the DB that only selects the few fields you actually need, and not even return any of the unwanted fields from the database. This also allows you to take full advantage of any indexes you have on the underlying tables in the DB.
Just a thought.

LINQ IQueryable Nested Loops

I am trying to replicate this loop as an IQueryable:
foreach (var book in BookList)
{
foreach (var a in book.Authors)
{
if (a.AuthorId.ToString() == Id)
{
AuthorView = new AuthorViewModel(a, item.BookId);
}
}
}
I have tried:
var Test = _context.Book.Include(x => x.Authors)
SelectMany(y => y.Authors).ToList().FirstOrDefault(x => x.AuthorId.ToString() == Id)
.Select(x => new AuthorViewModel(x, ??.BookId);
But I get stuck trying to create the object AuthorViewModel as it requires the BookId that I can no longer access. Is there a way of passing the book Id down?
Sorry, just release the example doesn't make sense.
Books is a table in a database.
Authors is a table in the database which contains many authors.
Book:
BookId
Authors
Authors:
AuthorId
Name
DOB
AuthorList should have been BookList (List). I have corrected this.
I am trying to locate an author based on an author id - and return it in an object (Author, BookId (from the book table))
This is I would do:
var id= int.Parse(Id);
var Test = _context.Book.Include(x => x.Authors)
.SelectMany(item => item.Authors
.Where(y => y.AuthorId == id)
.Select(b => new AuthorViewModel(b,item.BookId));
You can filter and project inside of the SelectMany extension method
I think you don't need SelectMany or Include at all. You can go like;
_context.Book
.Where(b => b.Authors.Any(a => a.AuthorId.ToString() == Id))
.Select(b => new AuthorViewModel(b.Authors.FirstOrDefault(x => x.AuthorId.ToString() == Id), b.BookId);
If you drill down on the Authors (SelectMany) then you have no way to go back to books. The condition on AuthorId needs to be handled internally.
var Test = _context.Book.Include(x => x.Authors)
.FirstOrDefault(x => x.Authors.Any(y => y.AuthorId.ToString() == Id))
.Select(x => new AuthorviewModel(x,x.BookId);
of course this will select only one book per author, just like your code. I wonder if your desired behaviour is to get all books instead...
First of all, convert Id to a number instead of trying to convert AuthorId to a string. Applying any kind of function on a table field means that indexes that cover it can't be used. Instead of a fast search based on an ID, you'll have a full table scan that converts values to strings before comparing them using string semantics. If the Id parameter contains any leading zeros, the comparison will fail.
As for the LINQ query itself, the easiest way is to use the query form. If you want to return one record for each book and author combination, you can write :
var id=int.Parse("ID");
var query= from book in _context.Book
from author in book.Authors
where author.AuthorID = id
select new AuthorViewModel(author,book.BookId);
var records=query.ToList();

Why Include() not available when Single() is used in entity framework code first fluent api

I wonder why Include() cannot be used after Single() in eager-loading. For example, in the following snippet, Include is not available:
db.Teachers.Single(p => p.Id == currUserId)
.Include(t => t.OfferedCourses)
.RegisteredCourses
.ToList();
However, it would work if I have Single() just after Include():
db.Teachers.Include(t => t.OfferedCourses)
.Single(p => p.Id == currUserId)
.RegisteredCourses
.ToList();
This way, many unnecessary related data will be returned. The following is the method I ended up with:
db.Teachers.Where(p => p.Id == currUserId)
.Include(t => t.OfferedCourses)
.First()
.RegisteredCourses
.ToList();
Is this the only solution?
The following is an answer to your question from the title of the post:
Why Include() not available when Single() is used in entity framework code first fluent api
.Include() is an extension method in Linq-To-Entities that expects type IQueryable.
When you specify db.Teachers.Include() or db.Teacher.Where().Include() you are satisfying this requirement because both db.Teachers and db.Teachers.Where() return type IQueryable.
But when you specify db.Teachers.Single() that returns type Teacher which won't work for the use of Include().

FQL: query for event table returns venue.name instead of venue.id

When making a query to get event details I seem to get venue.name instead of venue.id in the result set. Has there been an unannounced change in the table structure or am I doing something wrong. The Graph API Explorer gives the venue.id yet when using FQL through PHP SDK in my own web site it's the venue.name I get.
Heres the code:
$fql='{
"event_info": "SELECT name,description, pic_small,pic_big, eid,venue,location FROM event WHERE eid ='.$_GET['id'].'",
"event_venue":"SELECT name, username, page_id, location FROM page WHERE name IN (SELECT venue.id FROM #event_info)"
}';
$setup = array(
'method' => 'fql.multiquery',
'queries' => $fql,
'callback' => ''
);
$result = $facebook->api($setup);
This leads to the "event_venue" result set to be empty.
Here's the dump:
Array
(
[0] => Array
(
[name] => event_info
[fql_result_set] => Array
(
[0] => Array
(
[eid] => 410351692336116
[venue] => Array
(
[name] => Boothill
)
[location] => Boothill
)
)
)
[1] => Array
(
[name] => event_venue
[fql_result_set] => Array
(
)
)
)
If I test this query
SELECT name,description, pic_small,pic_big, eid,venue,location
FROM event WHERE eid ='410351692336116'
using the FQL tab (!) in the Graph API explorer, I get
"venue": {
"id": 126049334121592
}
and not the venue’s name.
And doing a multiquery like your’s with the second query being
"event_venue":"SELECT name, username, page_id, location FROM page
WHERE page_id IN (SELECT venue.id FROM #event_info)"
I’m getting the venue info as well.
Could you please check if you get a different result if you do your queries not using your $setup array with
'method' => 'fql.multiquery',
but just
$facebook->api('/fql?q='.urlencode('{ "event_info": "…", "event_venue": "… FROM page
WHERE page_id IN (SELECT venue.id FROM #event_info)" }'));
instead?
I've run into this issue today and spent quite some time troubleshooting it. It seems to be related to the Access Token. When I use my App's Access Token to request the venue data, for some venues all I I get is the venue.name field. However, if I use the Graph API Explorer to generate a different token, I get the venue.id field as expected.
I went as far as to replace the Graph API Explorer's generated Access Token with my App Token, and sure enough all I received back was venue.name.

Add Table Join, Where, and Order By to Views Query in views_query_alter()

I am trying to modify the query for Views in Drupal (Views version 3, Drupal version 7).
What I want to do is change the query prior to running such that it LEFT JOINs a table in which I have weights assigned to the nodes.
If I was to write the query I want in SQL, it would look like this:
SELECT a.nid, a.title, a.description
FROM node a
LEFT OUTER JOIN node_weights b
ON a.nid = b.nid
WHERE b.uid = $uid
ORDER BY b.weight DESC
This query works like a champ when I run it in the query analyzer. So, now I need to make it work in my module.
I've seen multiple approaches detailed on various blogs for different ways to modify View queries, but they seem to be addressing different versions of Views. So it is very confusing to try to determine whether anything I'm looking at could even possibly work for my application.
It seems that I need to use a MODULE_NAME_views_tables() function to tell Views what the relationship is between the table I want to join and the node table.
I've added the following functions to MODULE_NAME.views.inc:
function MODULE_NAME_views_tables() {
$tables['node_weights'] = array(
"name" => "node_weights",
"join" => array(
"left" => array(
"table" => "node",
"field" => "nid"
),
"right" => array(
"field" => "nid"
),
),
);
return $table;
}
This does seem to be working because when I use Krumo to look at the query array, I see my "node_weights" table in the "table_queue" element.
In the views_query_alter() function, I'd like it to work something like this:
function MODULE_NAME_views_query_alter(&$view, &$query) {
$uid = $_COOKIE['uid'];
$view->query->add_relationship('node_weights', new views_join('node_weights', 'nid', 'node', 'nid','LEFT'));
$view->query->add_where('node_weights', "node_weights.uid", $uid);
krumo($query);
}
This function barfs pretty badly. Although my join table is appearing in the $view object, the add_relationship method is throwing an error for a 3rd argument, but I don't see any examples online that have 3 arguments so I don't know what it's missing.
Also, I'm pretty sure my add_where method isn't correct, but I don't know what the inputs should actually be. This is just a blind guess.
The bottom line is that I want to join the node table to my node_weights table, and then make sure my weights are used in the query to sort the results in a descending fashion where the user id = the user id in my table, and the tables are joined on the nid field.
Thanks in advance.
WHEREs are pretty easy to add once you've got the JOIN in. You can both in a query alter (Drupal 7).
function MODULE_NAME_views_query_alter(&$view, &$query){
// Only alter the view you mean to.
if($view->name == 'VIEW NAME' && $view->current_display == 'DISPLAY'){
// Create the join.
$join = new views_join();
$join->table = 'table_name';
$join->field = 'entity_id';
$join->left_table = 'node';
$join->left_field = 'nid';
$join->type = 'left';
// Add the join the the view query.
$view->query->add_relationship('table_name', $join, 'node');
// Add the where.
$view->query->where[1]['conditions'][] = array(
'field' => 'table_name.collumn_name',
'value' => 'value',
'operator' => '='
);
}}
I found the OP's comments helpful in creating a join in the hook_views_query_alter function, so I wanted to put the parts I found useful in a more digestible answer.
I was using Views 2x on Drupal 6x, but I assume it would be very similar to use on D7 Views 2.
The OP mentions describing the relationship of the join in hook_views_table. This wasn't necessary for me, as I was not linking to a custom table, but one that existed in core.
The join creation in the HOOK_views_query_alter() function was very helpful though:
$join = new views_join;
$join->construct('table_name',
'node', // left table
'nid', // left field
'nid', // field
)
See views_join::construct documentation for more information. In particular, I didn't need to use the 'extra' parameter that the OP used. Perhaps this is necessary with a custom table.
Finally, add the join to the query, and whatever other elements are needed from it:
// Add join to query; 'node' is the left table name
$view->query->add_relationship('table_name',$join,'node');
// Add fields from table (or where clause, or whatever)
$view->query->add_field('table_name','field_name');
...
You already have the $query in parameters, so you can just do:
myhook_views_query_alter(&$view, &$query) {
if ($view->name = ....) {
$join = new views_join();
$join->construct(
...
);
$query
->add_relationship(...)
->add_where(...)
->add_orderby(...)
...
;
}
No need to use $view->query->...

Resources