Re-Apply Linq to entities from last Iqueryable - c#-4.0

here my issue:
I've an IQueryable object and I need to execute a new query on Db (just like a refresh) by launching the same contained query in my IQueryable obj.
An example:
myObj = objCtx.Person
.Where(p => p.IdPerson...)
.OrderBy(p => ...)
.Select(..some field..);
//...
// From another function I just want re-execute
// the same query
// -> Well, that retrieve full lambda
var et = this.myObj.Expression
// This doesn't work:
var anotherObj = objCtx.Person.Where(et);
//or..
var tmp = Expression.Lambda<Func<T, bool>>(et);
// This doesn't work too:
var anotherObj = objCtx.Person.Where(tmp);
Is it possibile to achieve? What am I missing?
Thanks
Ok:
I would be able to retrieve full query (select statement) from an IQueryable object and execute it in order to get all my updated data.
This is the original query:
this.myIQueryableObj = objCtx.Person
.Where(p => p.IdPerson...)
.OrderBy(p => ...)
.Select(..some field..);
//..And in some button click, I want execute again the above query, but all what I know is myIQueryableObjOnly. Can you help me?

Related

Adding a raw select statement to the Knex query builder

Using Knex to make queries to my Postgres DB. I have a function that provides a "base" query using the Knex QueryBuilder. This works fine until I need to add something raw to the SELECT statement. From what I can tell, running .raw always wants to return a result. I just need it to be added to the QueryBuilder, though, so it can be executed by a different part of my app.
const baseQuery = knex
.select(newUserFields)
.from('users')
.leftJoin('user_roles', 'users.id', 'user_roles.user_id')
.leftJoin('roles', 'user_roles.role_id', 'roles.id')
.leftJoin('role_permissions', 'roles.id', 'role_permissions.role_id')
.leftJoin(
'permissions',
'permissions.id',
'role_permissions.permission_id'
)
.groupBy(
'users.id',
'users.email',
'users.name',
'users.status',
'users.created_at',
'users.password_reset_expiration',
'users.password',
'users.password_reset_token'
)
.orderBy('users.created_at', 'desc');
I need to add the following to the select:
knex.raw('to_json(array_agg(distinct roles.name)) as roleNames')
knex.raw('to_json(array_agg(distinct permissions.name)) as permissionNames')
How can I add these raw selects to the base query so that the base query can then be passed to a different function as a QueryBuilder and added to?
The cool thing about knex is that it is a queryBuilder, which allows you to call the methods without any limits about the order of calls. That means that you can just construct your base query in a function, and then attach to it additional things (such as additional columns).
In your case you just can call another time to select (knex will join the select calls)
// base-query.js
export const getBaseQuery = () => knex
.select(newUserFields)
.from('users')
.leftJoin('user_roles', 'users.id', 'user_roles.user_id')
.leftJoin('roles', 'user_roles.role_id', 'roles.id')
.leftJoin('role_permissions', 'roles.id', 'role_permissions.role_id')
.leftJoin('permissions', 'permissions.id', 'role_permissions.permission_id')
.groupBy(
'users.id',
'users.email',
'users.name',
'users.status',
'users.created_at',
'users.password_reset_expiration',
'users.password',
'users.password_reset_token'
)
.orderBy('users.created_at', 'desc');
// other-file.js
import {getBaseQuery} from 'base-query';
const enhancedQuery = getBaseQuery().select([
knex.raw('to_json(array_agg(distinct roles.name)) as roleNames'),
knex.raw('to_json(array_agg(distinct permissions.name)) as permissionNames'),
]);
const results = await enhancedQuery;
Another cool way that I'm using heavily, solves the following requirement: Sometimes I need to change an internal query from the outside, I use the modify
For example, I have a getProducts method which execute a select query and do some data transformation.
In order to implement getProductById which needs to return the same data structure (it is just need to filter the base query) I pass a queryModifier method which modifies the original query.
async function getProducts(queryModifier) {
const products = await knex
.select('*')
.from('products')
.modify((queryBuilder) => {
if (typeof queryModifier === 'function') {
return queryModifier(queryBuilder);
}
});
return products.map(someDataTransformation);
}
async function getProductById(id) {
return getProducts((qb) => {
return qb.where('id', id);
});
}

Queries being run twice on sql.js

I'm using SQLite with sql.js on my project and I have been having some trouble with my implementation. Seems like the queries are being run on the database twice because for the for the INSERT statements I get 2 records in the DB.
The way I do it, I create the SQL and then pass it on to this method (the opts variable contains all of the data being put into the database):
prepareStatementAndCompileResults(db, sql, opts){
const stmt = db.prepare(sql);
const result = stmt.getAsObject(opts);
var rows = [];
if(!this.isEmpty(result)){ // isEmpty is a simple method that checks for empty objects
rows.push(result);
}
while(stmt.step()) {
var row = stmt.getAsObject();
rows.push(row);
}
this.saveToFile(db);
stmt.free();
return rows;
},
Here is a sample SQL INSERT that is being run twice
INSERT OR IGNORE INTO tag_event (tag_id, event_id, unique_string)
VALUES (:tag_id,:event_id, :unique);
Here is what the opts variable would look like for this query:
var opts = {
[':tag_id']: 1,
[':event_id']:1,
[':unique']: '1-1'
}
Because you're pushing it into row 2 time.
// if not empty will add to row
if(!this.isEmpty(result)){ // isEmpty is a simple method that checks for empty objects
rows.push(result);
}
// not sure what step() does but I'm assuming this will also run
while(stmt.step()) {
var row = stmt.getAsObject();
rows.push(row);
}
Verify by using a debugger or just console.log(rows) after the while loop before the save
So, what it turns out I needed to do was bind the variables to the prepared statement before getting the rather than binding them through getAsObject. This is much more efficient. My API response time on a local test went from 785ms to 14.5ms
prepareStatementAndCompileResults(db, sql, opts){
const rows = [];
const stmt = db.prepare(sql);
stmt.bind(opts);
while(stmt.step()) {
var row = stmt.getAsObject();
rows.push(row);
}
this.saveToFile(db);
stmt.free();
return rows;
},

Exception in OrmLite: Must declare the scalar variable

Our code has a SqlExpression, which at its bare minimum is something like:
var q = db.From<Users>();
q.Where(u => u.Age == 25);
totalRecords = db.Scalar<int>(q.ToCountStatement());
q.ToCountStatement() generates the following query:
SELECT COUNT(*) FROM "Users" WHERE ("Age" = #0)
However, db.Scalar() throws an exception: Must declare the scalar variable "#0". This has started occurring in recent versions (tested in 4.0.54). The same code was working fine until v4.0.50. I've checked the release notes, but couldn't find a related change.
Even passing a parameter throws the same exception:
totalRecords = db.Scalar<int>(q.ToCountStatement(), 25);
Is it a bug, or my oversight?
Secondly, is it possible to get q.ToCountStatement() to generate a more optimized query with COUNT(Age) or COUNT([PrimaryKey]) instead of COUNT(*)?
Now that OrmLite defaults to parameterized queries you also need to provide the queries db parameters when executing a query (if you've specified any params), e.g:
var q = db.From<Users>().Where(u => u.Age == 25);
var count = db.Scalar<int>(q.ToCountStatement(), q.Params);
You can also use OrmLite's explicit Count() API's, e.g:
db.Count<User>(x => x.Age == 25);
Or with a typed SqlExpression:
var q = db.From<User>().Where(x => x.Age == 25);
db.Count(q);
Otherwise another way to specify db params is to use an anonymous object, e.g:
db.Scalar<int>("SELECT COUNT(*) FROM Users WHERE Age=#age", new { age = 25});

Linq Invalid Cast Exception Same Object Type

I wrote this query and as my understanding of the business rules has improved I have modified it.
In this most recent iteration I was testing to see if indeed I had some redundancy that could be removed. Let me first give you the query then the error.
public List<ExternalForums> GetAllExternalForums(int extforumBoardId)
{
List<ExternalForums> xtrnlfrm = new List<ExternalForums>();
var query = _forumExternalBoardsRepository.Table
.Where(id => id.Id == extforumBoardId)
.Select(ExtForum => ExtForum.ExternalForums);
foreach (ExternalForums item in query)
{
xtrnlfrm.Add(new ExternalForums { Id = item.Id , ForumName = item.ForumName, ForumUrl = item.ForumUrl });
}
return xtrnlfrm;
}
Now in case it isn't obvious the query select is returning List of ExternalForums. I then loop through said list and add the items to another List of ExternalForums object. This is the redundancy I was expecting to remove.
Precompiler was gtg so I ran through it one time to very everything was kosher before refactoring and ran into a strange error as I began the loop.
Unable to cast object of System.Collections.Generic.HashSet
NamSpcA.NamSpcB.ExternalForums to type NamSpcA.NamSpcB.ExternalForums.
Huh? They are the same object types.
So am I doing something wrong in the way I am projecting my select?
TIA
var query = _forumExternalBoardsRepository.Table
.Where(id => id.Id == extforumBoardId)
.Select(ExtForum => ExtForum.ExternalForums);
This query returns IEnumerable<T> where T is type of ExtForum.ExternalForums property, which I would expect to be another collection, this time of ExternalForum. And the error message matches that, saying you have IEnumerable<HashSet<ExternalForums>>.
If you need that collection of collections to be flattened into one big collection of ExternalForums use SelectMany instead:
var query = _forumExternalBoardsRepository.Table
.Where(id => id.Id == extforumBoardId)
.SelectMany(ExtForum => ExtForum.ExternalForums);

Reconstructing an ODataQueryOptions object and GetInlineCount returning null

In an odata webapi call which returns a PageResult I extract the requestUri from the method parameter, manipulate the filter terms and then construct a new ODataQueryOptions object using the new uri.
(The PageResult methodology is based on this post:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options )
Here is the raw inbound uri which includes %24inlinecount=allpages
http://localhost:59459/api/apiOrders/?%24filter=OrderStatusName+eq+'Started'&filterLogic=AND&%24skip=0&%24top=10&%24inlinecount=allpages&_=1376341370337
Everything works fine in terms of the data returned except Request.GetInLineCount returns null.
This 'kills' paging on the client side as the client ui elements don't know the total number of records.
There must be something wrong with how I'm constructing the new ODataQueryOptions object.
Please see my code below. Any help would be appreciated.
I suspect this post may contain some clues https://stackoverflow.com/a/16361875/1433194 but I'm stumped.
public PageResult<OrderVm> Get(ODataQueryOptions<OrderVm> options)
{
var incomingUri = options.Request.RequestUri.AbsoluteUri;
//manipulate the uri here to suit the entity model
//(related to a transformation needed for enumerable type OrderStatusId )
//e.g. the query string may include %24filter=OrderStatusName+eq+'Started'
//I manipulate this to %24filter=OrderStatusId+eq+'Started'
ODataQueryOptions<OrderVm> options2;
var newUri = incomingUri; //pretend it was manipulated as above
//Reconstruct the ODataQueryOptions with the modified Uri
var request = new HttpRequestMessage(HttpMethod.Get, newUri);
//construct a new options object using the new request object
options2 = new ODataQueryOptions<OrderVm>(options.Context, request);
//Extract a queryable from the repository. contents is an IQueryable<Order>
var contents = _unitOfWork.OrderRepository.Get(null, o => o.OrderByDescending(c => c.OrderId), "");
//project it onto the view model to be used in a grid for display purposes
//the following projections etc work fine and do not interfere with GetInlineCount if
//I avoid the step of constructing and using a new options object
var ds = contents.Select(o => new OrderVm
{
OrderId = o.OrderId,
OrderCode = o.OrderCode,
CustomerId = o.CustomerId,
AmountCharged = o.AmountCharged,
CustomerName = o.Customer.FirstName + " " + o.Customer.LastName,
Donation = o.Donation,
OrderDate = o.OrderDate,
OrderStatusId = o.StatusId,
OrderStatusName = ""
});
//note the use of 'options2' here replacing the original 'options'
var settings = new ODataQuerySettings()
{
PageSize = options2.Top != null ? options2.Top.Value : 5
};
//apply the odata transformation
//note the use of 'options2' here replacing the original 'options'
IQueryable results = options2.ApplyTo(ds, settings);
//Update the field containing the string representation of the enum
foreach (OrderVm row in results)
{
row.OrderStatusName = row.OrderStatusId.ToString();
}
//get the total number of records in the result set
//THIS RETURNS NULL WHEN USING the 'options2' object - THIS IS MY PROBLEM
var count = Request.GetInlineCount();
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
Request.GetNextPageLink(),
count
);
return pr;
}
EDIT
So the corrected code should read
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
request.GetNextPageLink(),
request.GetInlineCount();
);
return pr;
EDIT
Avoided the need for a string transformation of the enum in the controller method by applying a Json transformation to the OrderStatusId property (an enum) of the OrderVm class
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus OrderStatusId { get; set; }
This does away with the foreach loop.
InlineCount would be present only when the client asks for it through the $inlinecount query option.
In your modify uri logic add the query option $inlinecount=allpages if it is not already present.
Also, there is a minor bug in your code. The new ODataQueryOptions you are creating uses a new request where as in the GetInlineCount call, you are using the old Request. They are not the same.
It should be,
var count = request.GetInlineCount(); // use the new request that your created, as that is what you applied the query to.

Resources