I want to build filters on IQueryable depending on user input and execute the query only at the end. I'm trying to understand concept behind and if this is going to work as expected.
Will the query below hit database on return fetchedWorkflowLogs.ToList() in the example below?
// Partition latest record for Instance
IQueryable<WorkflowLog> fetchedWorkflowLogs
= this._workflowLog_repo
.GetAll()
.GroupBy(log => log.ID)
.Select(
grp => new
{
grp = grp,
MaxID = grp.Max(log => log.ID)
}
)
.SelectMany(
temp0 => temp0.grp,
(temp0, log) => new
{
temp0 = temp0,
log = log
}
)
.Where(temp1 => (temp1.log.ID == temp1.temp0.MaxID))
.Select(temp1 => temp1.log);
// .. some more filters
// Last filter
// Filter by Project
if (model.ProjectID != null)
{
fetchedWorkflowLogs.Where(record => record.Project.ID == model.ProjectID);
}
return fetchedWorkflowLogs.ToList();
Agree with David. if GetAll() returns IQueryable, and you return fetchedWorkflowLogs.ToList() will result in the linq query being evaluated, and the query will hit database.
It's the best if you can debug and step through, and writing a test could help you if execuing program directly is difficult.
Related
I have a method with the following signatures that return a C# expression
Expression<Func<T, bool>> GetExpression<T>(IList<Filter> filters)
Then the following code that uses Dynamic LINQ
using (TestContext tEntities = new TestContext())
{
var filterExp = Exp.ExpressionBuilder.GetExpression<Client>(filters);
var filteredCollection = tEntities.Client.Where(filterExp);
IQueryable<Client> queryResult;
if (filterExp == null)
queryResult = tEntities.Client;
else
queryResult = tEntities.Client.Where(filterExp);
}
This is a simple scenario.
I have queries that are 50 lines long sometimes more. I want to avoid having the same code twice with only difference using the where clause.
Does anyone knows if I achieve something different?
from product in context.Product.Where(deleg)
.Include(x => x.Type)
.Include(x => x.Category)
.Include(x => x.WareHouse)
.Include(x => x.Photos)
join f in context.Favorite on product.Id equals f.ProductFid into fg
from fgi in fg.Where(f => f.UserFid == userId).DefaultIfEmpty()
orderby product.Id descending
select new ProductngDto()
{
ProductItem = product,
FavoriteId = fgi != null ? fgi.Id : (long?)null
}).Skip(page * pageSize).Take(pageSize);
Thanks in advance
One of the nice things about IQueryable<T> and IEnumerable<T> is the fact that they are so abstract, you can easily chain them together. One solution could be to structure your code like this:
using (TestContext tEntities = new TestContext())
{
var filterExp = Exp.ExpressionBuilder.GetExpression<Client>(filters);
var filteredCollection = tEntities.Client.Where(filterExp);
IQueryable<Client> queryResult = tEntities.Client;
if (filterExp != null)
{
queryResult = queryResult.Where(filterExp);
}
//do something else with queryResult
}
This way you can continue using queryResult without having to know, or even care, about whether or not filterExp was applied.
For the second example, shuffling it around could look something like this:
var query = from p in context.Product
.Include(x => x.Type)
.Include(x => x.Category)
.Include(x => x.WareHouse)
.Include(x => x.Photos);
if (deleg != null)
{
query = query.Where(deleg);
}
query = from product in query
join f in context.Favorite on product.Id equals f.ProductFid into fg
from fgi in fg.Where(f => f.UserFid == userId).DefaultIfEmpty();
orderby product.Id descending
select new ProductngDto()
{
ProductItem = product,
FavoriteId = fgi != null ? fgi.Id : (long?)null
}).Skip(page * pageSize).Take(pageSize);
Another option you have, is to check the filterExp for null and assign an "always true" lambda to it.
If you control the GetExpression<T> method, you could add it in there as a last step. If you don't control it, you could do the null checking in the methods where you use it's result.
This will allow you to keep your code looking a bit cleaner, but the trade-off is a small performance hit for having to evaluate the lambda all the time.
Depending on how your expressions are typed, this might be as easy as:
if (filterExp == null)
{
filterExp = (_) => true;
}
I am constructing a reactive pipeline that needs to expand (SelectMany) and then flatten (in this case, ToArray) whilst maintaining access to a piece of state obtained at the beginning of the pipeline.
Here is pseudo-code for what I am attempting:
return Observable
.Start(() => this.GetSearchResults(query))
.SelectMany(results => results.Hits) // results.Hits is a list of IDs. But there is also has a bool property that I want to keep through to the end of my pipeline
.SelectMany(hit => GetById(hit.Id)) // asynchronously load each result
.ToArray() // now need to pull all the results together into a containing data structure, and also include the bool flag from above in it
.Select(resolvedResults => new ...); // need access to both resolvedResults and the bool mentioned in the first comment above
So I'm trying to find a way to cleanly access some state determined at the beginning of the pipeline from the code at the end of the pipeline.
The first thing I tried was using anonymous types to bundle the bool with each result. This quickly got out of hand and was wasteful from a performance perspective.
The second thing I tried was using a subject as follows:
var state = new AsyncSubject<bool>();
return Observable
.Start(() => this.GetSearchResults(query))
.Do(results =>
{
state.OnNext(results.Flag);
state.OnCompleted();
}
.SelectMany(results => results.Hits)
.SelectMany(hit => GetById(hit.Id))
.ToArray()
.Zip(
state,
(results, state) => new ResultContainer(state, results));
This seems to work fine, but feels a little icky to me.
So what I'm wondering is whether there is a cleaner way to manage state in a reactive pipeline.
For reference, here is the actual code (rather than just pseudo-code):
public IObservable<ISearchResults<IContact>> Search(string query, int maximumResultCount = 100, float minimumScore = 0.1F)
{
Ensure.ArgumentNotNull(query, nameof(query));
var moreHitsAvailable = new AsyncSubject<bool>();
return Observable
.Start(
() => this.searchIndexService.Search<IContact>(query, maximumResultCount, minimumScore),
this.schedulerService.DataStoreScheduler)
.Do(
results =>
{
moreHitsAvailable.OnNext(results.MoreHitsAreAvailable);
moreHitsAvailable.OnCompleted();
})
.SelectMany(
results => results
.Hits
.Select(
hit => new
{
Id = hit.Id,
ParsedId = ContactId.Parse(hit.Id)
}))
.SelectMany(
result => this
.GetById(result.ParsedId)
.Select(
contact => new
{
Id = result.Id,
Contact = contact
}))
.Do(
result =>
{
if (result.Contact == null)
{
this.logger.Warn("Failed to find contact with ID '{0}' provided by the search index. Index may be out of date.", result.Id);
}
})
.Select(result => result.Contact)
.Where(contact => contact != null)
.ToArray()
.Zip(
moreHitsAvailable,
(results, more) => new SearchResults<IContact>(more, results.ToImmutableList()))
.PublishLast()
.ConnectUntilCompleted();
}
You could pop out to Query Comprehension Syntax and do something like this
var x = from result in Observable.Start(() => this.GetSearchResults())
let hasMore = result.MoreHitsAreAvailable
from hit in result.Hits
from contact in GetById(hit.Id)
select new { hasMore , contact};
Over to you how to deal with the duplicate hasMore values. As we know it will be just the single distinct value (all true or all false) you could group by.
I have a some C# code that is querying a database using nhibernate that looks like this:
public void Query()
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
I now have a number of user driver filters so, based on the parameters passed in, i want to add to the where clause. So something like this:
public void Query(string name)
{
if (!String.IsNullOrEmpty(name)
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted && p.Name == name)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
}
else
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
the user can select one or many filters. As you can imagine, this code above would get ridiculously complicated given the large number of combinations. Is there an elegant way to append a where clause here with additional blocks of logic. Some might be simple such as
p.Name == name
but others might be more complicated like:
p.ProjectApplications.Select(r => r.Application).Any(s => applicationIds.Contains(s.Id)))
and as I said, there may be zero or many different filters . .
UPDATE:
i have seen in other cases, people suggesting building up the where clause like
query = query.where (r=>r.name = "XYZ");
query = query.where (r=>r.Age > 10);
query = query.where (r=>r.Gender = "Male");
but that does NOT seem to work with nhibernate so what started was a generic lambda question is now a specific question to nhibernate
You can use the PredicateBuilder<T> to create the expression and apply it on your query, for sample:
public void Query(string name)
{
Expression<Func<Project, bool>> filter = PredicateBuilder.True<Project>();
filter = filter.And(p => !p.IsDeleted);
if (!string.IsNullOrEmpty(name)
filter = filter.And(p => p.Name == name);
IEnumerable<Project> list = session.Query<Project>()
.Where(filter)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList();
}
With PredicateBuilder you can create the expression you need, adding conditions using And(), Or(), Not() methods.
If you are looking something like this:
public IList<Bestellung> GetAll(Expression<Func<Order, bool>> restriction)
{
ISession session = SessionService.GetSession();
IList<Order> bestellungen = session.Query<Order>()
.Where(restriction).ToList();
return bestellungen;
}
Read this.
Normally it should work and i saw on forum questions like this but for me it is very strange and the where conditions are not addes although they are executed.
Here is my code:
var query = context.infoes.Join(context.users, u => u.Id, a => a.Id, (u, a) => new { U = u, A = a }).
Where(j=>j.A.IA==true).
Where(j=>j.U.G==(int)model.G).
Where(j=>j.U.CI==model.CI);
if (MA != null)
{
query.Where(j => j.U.BD < MD);
}
if (MIA != null)
{
query.Where(j => j.U.BD > MIA);
}
query.Where(j => j.U.Id == 10);
int countResults = query.Count();
This last where i've just added to be sure, but it is not added either. So after the first line of code in intermidiate window i put query and i saw the sql, so i've let the code run and add the other where, but the query didn't change it still defined only the base conditions.
Am i doing something wrong?
Just assign the conditions back to query, like
query = query.Where(j => j.U.BD < MD);
I have this expression, which generates single query to the database:
db = new MyDataContext();
var productInCity = db.Products
.Where(n => n.id == 2)
.Select(k => new ProductInCityDto()
{
ProductName = k.ProductName,
CityName = k.Store.City.Name,
CountryName = k.Store.City.Country.Name
.
.
.
})
.FirstOrDefault();
I want to make this code cleaner, by putting the mapping in a function, extension method or in the object's constructor, something like this:
db = new MyDataContext();
var productInCity = db.Products
.Where(n => n.id == 2)
.Select(k => new ProductInCityDto(k))
.FirstOrDefault();
But, in this case, multiple queries to the DB are generated (I use LinqToSql Profiler).
Is there a way to isolate the mapping (Select statement) in order to achieve better code readability?
YES, if you look at the actual signature of the Select extension method on IQueryable you will find that it does not take a function but an Expression>.
So, just do that...
Expression<Func<Product, ProductInCityDto>> MyMappingExpression
{
get
{
return product => new ProductInCityDto
{
...
}
}
}
and then
db = new MyDataContext();
var productInCity = db.Products.Where(n => n.id == 2)
.Select(MyMappingExpression)
.FirstOrDefault();
If you need to use MyMappingExpression in process you will likely want to convert it to a
Func<Product, ProductInCityDto>
by calling the Expression.Compile() method.
Instead of creating mappings by hand, you may use AutoMapper. But if you do not want to create the mapping using a third party tool just change the query to the following;
var productInCity = new ProductInCity(
db.Products.Include("Store").SingleOrDefault(n => n.id == 2));