Compilation error with QueryOver and multiple SubQueries - nhibernate

I'm getting the following compilation error when I'm using QueryOver with a list of sub queries:
"The type arguments for method 'xxxx' cannot be inferred from the usage. Try specifying the type arguments explicitly."
Here is the code but not sure how I can correct it:
List<QueryOver> subQueries = new List<QueryOver>();
subQueries.Add(QueryOver.Of<Customer>().Where(...));
subQueries.Add(QueryOver.Of<Address>().Where(...));
subQueries.Add(QueryOver.Of<Account>().Where(...));
var query = session.QueryOver<Customer>();
foreach (QueryOver subQuery in subQueries)
{
query.WithSubquery.WhereProperty(c => c.CustomerID)
.In(subQuery); // this is throwing the compilation error
}
var result = query.Select(Projections.RowCount())
.FutureValue<int>()
.Value;
I need to do this programatically as I am generating the subQueries dynamically and don't know how many sub queries there will be. Do I need to use dynamic types or something?

I think you can get around this by rewriting things slightly:
foreach (QueryOver subQuery in subQueries)
{
query.Where(
Restrictions.EqProperty(
Projections.Property<Customer>(c => c.CustomerID),
Projections.SubQuery(subQuery.DetachedCriteria)));
}
I don't know enough about your subqueries to say, but you might be able to use a List<QueryOver<Customer>> instead. The issue is that .WithSubquery...In requires a QueryOver<U> where your list is the non-generic base type (QueryOver).

I've managed to solve this with a little refactoring. Andrew's response gave me the idea to use .Where() (instead of .WithSubquery) and then use a Conjunction for the sub queries. The refactored code looks like this:
Conjunction conj = new Conjunction();
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<Customer>()...));
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<AttributeValue>()...));
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<CustomerListEntry>()...));
ISession session = sf.OpenSession();
using (var tran = session.BeginTransaction())
{
var query = session.QueryOver<Customer>()
.Where(conj)
.Select(Projections.RowCount())
.FutureValue<int>()
.Value;
tran.Commit();
}
I can now programatically build up and selectively apply the subqueries.

Related

How to add post processing to .Net Core OData?

I have an OData Controller which looks pretty standard.
[HttpGet]
[ODataRoute("GridData")]
[EnableQuery]
public async Task<IQueryable<GridData>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
var query = odataOptions.ApplyTo(_service.GetGridDataQueryable()) as IQueryable<GridData>
return query;
}
Projection looks like this :
.Select(async x =>
{
//Pretty resource heavy
x.Ownership = await _ownershipService.ComputeAsync(_currentUser));
return x;
})
.Select(t => t.Result)
.ToList();
Now the problem is that I need to actually return a GridDataDTO object from this call. There is some processing that cannot be done at the database level. The processing is pretty heavy so I would not like to add it inside the GetGridDataQueryable().Also the processing is async, and need a materialized result set to be able to apply it.
I also need to return the IQueryable in the controller to be able to benefit from $count, $select, etc .
This hooks up to a pretty complex grid with a lot of options for filtering/sorting so I would not like to remove the OData functionality.
Is there a simple way to add postprocessing here ? After the result is materialized, project it to my GridDataDTO ?
There is no need for insert/update/delete support, as this will be only used for read operations.
There is no requirement for your controller method to only pass through a query from the database, in fact your method does not need to return an IQueryable<T> result at all!
You can still benefit from OData $select, $expand and $filter operators on result sets that are not IQueryable<T>, but you lose most of the performance benefits of doing so and you have to prepare you data so that the operators can be processed, and you will have to explicitly decorate your endpoint with the [EnableQuery] attribute.
In the following example you current query is materialized into memory, after applying the query options, then we can iterate over the set and manipulate it as we need to.
In the end the same recordset, with the modified records is returned, cast as queryable to match the method signature, however the method would still function the same if the result was IEnumerable<T>
There is a strong argument that says you should return IEnumerable<T> because it conveys the correct information that the recordset has been materialized and is not deferred.
[HttpGet]
[ODataRoute("GridData")]
public async Task<IQueryable<GridDataDTO>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
// NOTE: GridDataDTO : GridData
// apply $filter, $top and $skip to the DB query
IQueryable<GridData> query = odataOptions.ApplyTo(_service.GetGridDataQueryable());
// materialize
var list = query.ToList();
// project into DTO
List<GridDataDTO> output = list.Select(async x =>
{
var o = new GridDataDTO(x);
o.Ownership = await _ownershipService.ComputeAsync(_currentUser));
}).ToList();
// return, as Queryable
return output.AsQueryable();
}
UPDATE:
When the manipulations involve projection into a new type, then to properly support OData query options the type defined in your ODataQueryOptions<> needs to be assignable from the output element type. You can do this through inheritance or with implicit cast definitions.
If an explicit cast is required (or no cast is available at all) then you will have to manually validate the ApplyTo logic, the ODataQueryOptions must be a valid type reference to match the output.

RavenDb LuceneQuery: How to query on List<string>

How can I make a query on List<string> with Lucene? Here is my attempt, but I got an error.
var top10MoviesQuery = session.Advanced.DocumentQuery<Media, Media_Index>()
.SetResultTransformer("MediaListTransformer")
.SelectFields<MediaListProjection>()
.WhereEquals(o => o.AvalibleOnServices, serviceMovie) <---- here
.OrderByDescending(o => o.OurScore)
.Take(10)
.Lazily();
AvalibleOnServices is a List<string>() and serviceMovie is a string.
I'm assuming your purpose is to query documents which AvailableOnServices list contains serviceMovie. If that's the case you could just do something like this, you don't need Lucene for such a simple thing:
var top10MoviesQuery = session.Query<Media, Media_Index>()
.Where(x => x.AvailableOnServices.Contains(serviceMovie))
.Take(10)
.TransformWith<MediaListTransformer, MediaListProjection>()
.Lazily();
Note that:
Here I assume that MediaListProjection is what MediaListTransformer transforms into, since you didn't mention otherwise, and
you need to add using Raven.Client.Linq; to be able to invoke TransformWith on the results - by importing this namespace you will get an IRavenQueryable<T> instead of a regular IQueryable<T> when you call Where, so you will be able to invoke Raven-specific stuff such as TransformWith and Lazily.

How to build LINQ queries for NHibernate which contain function calls?

A few weeks ago I decided to switch from using Linq to SQL to use NHibernate instead. (Reasons include: other Java-based projects use Hibernate; target DB not yet decided; multiple DBs may have to be targeted)
Anyway, I wanted to continue using LINQ and saw that NHibernate has LINQ support. I want to have a small nummber of object access methods and be able to pass a LINQ expression which filters the query but it has not worked as expected.
Here is an example based on the PredicateBuilder from http://www.albahari.com/nutshell/predicatebuilder.aspx
public static Expression<Func<Product, bool>> ContainsInDescription(params string[] keys)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keys)
{
string temp = keyword.ToLower();
predicate = predicate.Or(p => p.Description.Contains(temp));
}
return predicate;
}
The Predicate.False statment causes the following error message:
Atf.NUnit.Model.TestLinq.TestProductCID():
System.Exception : Could not determine member type from Constant, False, System.Linq.Expressions.ConstantExpression
The p.Description.Contains statement causes this error message:
Atf.NUnit.Model.TestLinq.TestProductCID():
System.Exception : Could not determine member type from Invoke, Invoke(p => p.Description.Contains(value(Atf.Model.Linq.ProductLinq+<>c__DisplayClass2).temp), f), System.Linq.Expressions.InvocationExpression
I get similar errors when using string.Equals and other such methods.
Am I doing something wrong here? Should I use a different approach?

WebAPI and OData - returning Queryable with preconditions

I have a simple GET method, which returns IQueryable, and has some preconditions on query:
[Queryable(HandleNullPropagation = HandleNullPropagationOption.False)]
public IQueryable<Message> Get()
{
using (var session = RavenStore.GetSession())
{
var messages = session.Query<Message>().Where(x => x.TargetUserId == this.User.Identity.Name || x.SourceUserId == this.User.Identity.Name);
return messages;
}
}
This is RavenDB, btw. The issue I'm having is that upon execution the user id is replaced with "[EMPTY_STRING]", so the actual query its running is this:
'TargetUserId:[[EMPTY_STRING]] OR SourceUserId:[[EMPTY_STRING]]' on
index .....
which is obviously wrong.
If I'm returning List instead of IQueriable - it works fine, so something later in the pipeline changes the query. Does anyone have any insight on how to make this work ?
It should work when the values are copied to a local variable first:
var userName = this.User.Identity.Name;
return session.Query<Message>()
.Where(x => x.TargetUserId == userName ||
x.SourceUserId == userName);
This is because by the time the query is executed, the Raven Client query translator can't resolve the objects expressed in the predicate. By copying them into a local variable, you are passing a constant value into the expression.
I believe this is related to closures. Perhaps someone with more direct knowledge of expression trees can explain better in comments.

How to intercept and modify SQL query in Linq to SQL

I was wondering if there is any way to intercept and modify the sql generated from linq to Sql before the query is sent off?
Basically, we have a record security layer, that given a query like 'select * from records' it will modify the query to be something like 'select * from records WHERE [somesecurityfilter]'
I am trying to find the best way to intercept and modify the sql before its executed by the linq to sql provider.
Ok, first to directly answer your question (but read on for words of caution ;)), there is a way, albeit a finicky one, to do what you want.
// IQueryable<Customer> L2S query definition, db is DataContext (AdventureWorks)
var cs = from c in db.Customers
select c;
// extract command and append your stuff
DbCommand dbc = db.GetCommand(cs);
dbc.CommandText += " WHERE MiddleName = 'M.'";
// modify command and execute letting data context map it to IEnumerable<T>
var result = db.ExecuteQuery<Customer>(dbc.CommandText, new object[] { });
Now, the caveats.
You have to know which query is generated so you would know how to modify it, this prolongs development.
It falls out of L2S framework and thus creates a possible gaping hole for sustainable development, if anyone modifies a Linq it will hurt.
If your Linq causes parameters (has a where or other extension causing a WHERE section to appear with constants) it complicates things, you'll have to extract and pass those parameters to ExecuteQuery
All in all, possible but very troublesome. That being said you should consider using .Where() extension as Yaakov suggested. If you want to centrally controll security on object level using this approach you can create an extension to handle it for you
static class MySecurityExtensions
{
public static IQueryable<Customer> ApplySecurity(this IQueryable<Customer> source)
{
return source.Where(x => x.MiddleName == "M.");
}
}
//...
// now apply it to any Customer query
var cs = (from c in db.Customers select c).ApplySecurity();
so if you modify ApplySecurity it will automatically be applied to all linq queries on Customer object.
If you want to intercept the SQL generated by L2S and fiddle with that, your best option is to create a wrapper classes for SqlConnection, SqlCommand, DbProviderFactory etc. Give a wrapped instance of SqlConnection to the L2S datacontext constructor overload that takes a db connection. In the wrapped connection you can replace the DbProviderFactory with your own custom DbProviderFactory-derived class that returns wrapped versions of SqlCommand etc.
E.g.:
//sample wrapped SqlConnection:
public class MySqlConnectionWrapper : SqlConnection
{
private SqlConnecction _sqlConn = null;
public MySqlConnectionWrapper(string connectString)
{
_sqlConn = new SqlConnection(connectString);
}
public override void Open()
{
_sqlConn.Open();
}
//TODO: override everything else and pass on to _sqlConn...
protected override DbProviderFactory DbProviderFactory
{
//todo: return wrapped provider factory...
}
}
When using:
using (SomeDataContext dc = new SomeDataContext(new MySqlConnectionWrapper("connect strng"))
{
var q = from x in dc.SomeTable select x;
//...etc...
}
That said, do you really want to go down that road? You'll need to be able to parse the SQL statements and queries generated by L2S in order to modify them properly. If you can instead modify the linq queries to append whatever you want to add to them, that is probably a better alternative.
Remember that Linq queries are composable, so you can add 'extras' in a separate method if you have something that you want to add to many queries.
first thing come to my mind is to modify the query and return the result in Non-LINQ format
//Get linq-query as datatable-schema
public DataTable ToDataTable(System.Data.Linq.DataContext ctx, object query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
IDbCommand cmd = ctx.GetCommand((IQueryable)query);
System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
adapter.SelectCommand = (System.Data.SqlClient.SqlCommand)cmd;
DataTable dt = new DataTable("sd");
try
{
cmd.Connection.Open();
adapter.FillSchema(dt, SchemaType.Source);
adapter.Fill(dt);
}
finally
{
cmd.Connection.Close();
}
return dt;
}
try to add your condition to the selectCommand and see if it helps.
Try setting up a view in the DB that applies the security filter to the records as needed, and then when retrieving records through L2S. This will ensure that the records that you need will not be returned.
Alternatively, add a .Where() to the query before it is submitted that will apply the security filter. This will allow you to apply the filter programmatically (in case it needs to change based on the scenario).