In the past I've dealt with optional search criteria by dynamically adding filters to a Linq query like this:
public IEnumerable<Customer> FindCustomers(string name)
{
IEnumerable<Customer> customers = GetCustomers();
var results = customers.AsQueryable();
if (name != null)
{
results = results.Where(customer => customer.Name == name);
}
results = results.OrderBy(customer => customer.Name);
return results;
}
or similarly using predicates, where you basically just move the lambda from the Where into a Func<> (or Expression<Func<>> if using LinqToEntities), like this:
public IEnumerable<Customer> FindCustomers(string name)
{
Func<Customer, bool> searchPredicate = customer => true;
if (name != null)
{
searchPredicate = customer => customer.Name == name;
}
IEnumerable<Customer> customers = GetCustomers();
var results = customers
.Where(searchPredicate)
.OrderBy(customer => customer.Name);
return results;
}
However I can't figure out how to do something similar when the Where clause is buried somewhere in a nested subquery. Consider the following (made up) scenario:
public class Customer
{
public string Name;
public int MaxOrderItemAmount;
public ICollection<Order> PendingOrders;
public ICollection<OrderItem> FailedOrderItems;
public ICollection<Order> CompletedOrders;
}
public class Order
{
public int Id;
public ICollection<OrderItem> Items;
}
public class OrderItem
{
public int Amount;
}
public IEnumerable<OrderItem> FindInterestingOrderItems(
bool onlyIncludePendingItemsOverLimit)
{
var customers = GetCustomersWithOrders();
// This approach works, but yields an unnecessarily complex SQL
// query when onlyIncludePendingItemsOverLimit is false
var interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(orderItem => onlyIncludePendingItemsOverLimit == false
|| orderItem.Amount > customer.MaxOrderItemAmount))
.Union(customer.FailedOrderItems)
);
// Instead I'd like to dynamically add the Where clause only if needed:
Func<OrderItem, bool> pendingOrderItemPredicate = orderItem => true;
if (onlyIncludePendingItemsOverLimit)
{
pendingOrderItemPredicate =
orderItem => orderItem.Amount > customer.MaxOrderItemAmount;
// PROBLEM: customer not defined here ^^^
}
interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(pendingOrderItemPredicate)
.Union(customer.FailedOrderItems)))
.OrderByDescending(orderItem => orderItem.Amount);
return interestingOrderItems;
}
Obviously I can't just move the lambda to a Func<> this time because it contains a reference to a variable (customer) defined by a higher-level part of the query. What am I missing here?
This works: building the predicate in a separate function, passing the "customer" value from the higher-level lambda as a parameter.
publicvoid FindInterestingOrderItems(bool onlyIncludePendingItemsOverLimit)
{
var customers = GetCustomersWithOrders();
var interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(GetFilter(customer, onlyIncludePendingItemsOverLimit))
.Union(customer.FailedOrderItems)))
.OrderByDescending(orderItem => orderItem.Amount);
}
private Func<OrderItem, bool> GetFilter(Customer customer, bool onlyIncludePendingItemsOverLimit)
{
if (onlyIncludePendingItemsOverLimit)
{
return orderItem => orderItem.Amount > customer.MaxOrderItemAmount;
}
else
{
return orderItem => true;
}
}
Related
Consider the the following query method:
internal List<Product> productInStockQuery(InStock inStock1)
{
var inStock =
from p in products
where p.INStock == inStock1
select p;
return inStock.ToList<Product>();
}
I would like the method to do the following:
If inStock1 == InStock.needToOrder I would like it to search for both (something like):
(instock1==InStock.needToOrder && instock1==InStock.False)
How can I do that?
I would also like to know if it is possible to create a method that allows me to search all of my Product fields in one Method.
EDITED SECTION: (second question i had)
trying to explain myself better: my class has several fields and right now for each field i have a method like shown above i wanted to know if it is possible to create a special variable that will allow me to acces each field in Product without actually typing the fields name or getters like instead of p.price / p.productName i would just type p.VARIABLE and depending on this variable i will acces the wanted field / getter
if such option exists i would appreciate it if you could tell me what to search the web for.
p.s
thanks alot for all the quick responses i am checking them all out.
Do you mean using something like an Enum?
internal enum InStock
{
NeedToOrder,
NotInStock,
InStock
}
internal class Product
{
public string Name { get; set; }
public InStock Stock { get; set; }
}
Then pass a collection to the method...
internal static List<Product> ProductInStockQuery(List<Product> products)
{
var inStock =
from p in products
where p.Stock != InStock.NeedToOrder && p.Stock != InStock.NotInStock
select p;
return inStock.ToList();
}
Create a list and pass it in...
var prods = new List<Product>
{
new Product
{
Name = "Im in stock",
Stock = InStock.InStock
},
new Product
{
Name = "Im in stock too",
Stock = InStock.InStock
},
new Product
{
Name = "Im not in stock",
Stock = InStock.NotInStock
},
new Product
{
Name = "need to order me",
Stock = InStock.NotInStock
},
};
var products = ProductInStockQuery(prods);
You can compose queries based upon an input variable like so:
var inStock =
from p in products
select p;
if(inStock1 == InStock.needToOrder)
{
inStock = (from p in inStock where p.INStock == InStock.needToOrder || instock1 == InStock.False select p);
}
else
{
inStock = (from p in inStock where p.INStock == inStock1 select p);
}
return inStock.ToList<Product>();
I believe you can use WhereIf LINQ extension method to make it more clear and reusable. Here is an example of extension
public static class MyLinqExtensions
{
public static IQueryable<T> WhereIf<T>(this IQueryable<T> source, Boolean condition, System.Linq.Expressions.Expression<Func<T, Boolean>> predicate)
{
return condition ? source.Where(predicate) : source;
}
public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, Boolean condition, Func<T, Boolean> predicate)
{
return condition ? source.Where(predicate) : source;
}
}
Below you can see an example how to use this approach
class Program
{
static void Main(string[] args)
{
var array = new List<Int32>() { 1, 2, 3, 4 };
var condition = false; // change it to see different outputs
array
.WhereIf<Int32>(condition, x => x % 2 == 0) // predicate will be applied only if condition TRUE
.WhereIf<Int32>(!condition, x => x % 3 == 0) // predicate will be applied only if condition FALSE
.ToList() // don't call ToList() multiple times in the real world scenario
.ForEach(x => Console.WriteLine(x));
}
}
q = (from p in products);
if (inStock1 == InStock.NeedToOrder)
q = q.Where(p => p.INStock == InStock.False || p.InStock == InStock.needToOrder);
else
q = q.Where(p => p.INStock == inStock1);
return q.ToList();
I'm looking for a way to get total price count from the Costs list in my object. I can't get Projections.Sum to work in my QueryOver so I tried another way but I'm having problems with it. I want to use a unmapped property in my QueryOver. I found this example but it's giving an error.
Object:
public class Participant
{
public int Id { get; set; }
public double TotalPersonalCosts { get { return Costs.Where(x => x.Code.Equals("Persoonlijk") && x.CostApprovalStatus == CostApprovalStatus.AdministratorApproved).Sum(x => x.Price.Amount); } }
public IList<Cost> Costs { get; set; }
}
The property TotalPersonalCosts is not mapped and contains the total price count.
Extension Class:
public static class ParticipantExtensions
{
private static string BuildPropertyName(string alias, string property)
{
if (!string.IsNullOrEmpty(alias))
{
return string.Format("{0}.{1}", alias, property);
}
return property;
}
public static IProjection ProcessTotalPersonalCosts(System.Linq.Expressions.Expression expr)
{
Expression<Func<Participant, double>> w = r => r.TotalPersonalCosts;
string aliasName = ExpressionProcessor.FindMemberExpression(expr);
string totalPersonalCostName = ExpressionProcessor.FindMemberExpression(w.Body);
PropertyProjection totalPersonalCostProjection =
Projections.Property(BuildPropertyName(aliasName, totalPersonalCostName));
return totalPersonalCostProjection;
}
}
My QueryOver:
public override PagedList<AccountantViewInfo> Execute()
{
ExpressionProcessor.RegisterCustomProjection(
() => default(Participant).TotalPersonalCosts,
expr => ParticipantExtensions.ProcessTotalPersonalCosts(expr.Expression));
AccountantViewInfo infoLine = null;
Trip tr = null;
Participant pa = null;
Cost c = null;
Price p = null;
var infoLines = Session.QueryOver(() => tr)
.JoinAlias(() => tr.Participants, () => pa);
if (_status == 0)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted || pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
else if (_status == 1)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted);
else
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
infoLines.WhereRestrictionOn(() => pa.Employee.Id).IsIn(_employeeIds)
.Select(
Projections.Property("pa.Id").WithAlias(() => infoLine.Id),
Projections.Property("pa.Employee").WithAlias(() => infoLine.Employee),
Projections.Property("pa.ProjectCode").WithAlias(() => infoLine.ProjectCode),
Projections.Property("tr.Id").WithAlias(() => infoLine.TripId),
Projections.Property("tr.Destination").WithAlias(() => infoLine.Destination),
Projections.Property("tr.Period").WithAlias(() => infoLine.Period),
Projections.Property("pa.TotalPersonalCosts").WithAlias(() => infoLine.Period)
);
infoLines.TransformUsing(Transformers.AliasToBean<AccountantViewInfo>());
var count = infoLines.List<AccountantViewInfo>().Count();
var items = infoLines.List<AccountantViewInfo>().ToList().Skip((_myPage - 1) * _itemsPerPage).Take(_itemsPerPage).Distinct();
return new PagedList<AccountantViewInfo>
{
Items = items.ToList(),
Page = _myPage,
ResultsPerPage = _itemsPerPage,
TotalResults = count,
};
}
Here the .Expression property is not found from expr.
I don't know what I'm doing wrong. Any help or alternatives would be much appreciated!
Solution with Projection.Sum() thx to xanatos
.Select(
Projections.Group(() => pa.Id).WithAlias(() => infoLine.Id),
Projections.Group(() => pa.Employee).WithAlias(() => infoLine.Employee),
Projections.Group(() => pa.ProjectCode).WithAlias(() => infoLine.ProjectCode),
Projections.Group(() => tr.Id).WithAlias(() => infoLine.TripId),
Projections.Group(() => tr.Destination).WithAlias(() => infoLine.Destination),
Projections.Group(() => tr.Period).WithAlias(() => infoLine.Period),
Projections.Sum(() => c.Price.Amount).WithAlias(() => infoLine.TotalPersonalCost)
);
You can't use unmapped columns as projection columns of a NHibernate query.
And the way you are trying to do it is conceptually wrong: the ParticipantExtensions methods will be called BEFORE executing the query to the server, and their purpose is to modify the SQL query that will be executed. An IProjection (the "thing" that is returned by ProcessTotalPersonaCosts) is a something that will be put between the SELECT and the FROM in the query. The TotalCosts can't be returned by the SQL server because the SQL doesn't know about TotalCosts
Is there a way to convertFunc<IQueryable<TD>, IOrderedQueryable<TD>> to Func<IQueryable<TE>, IOrderedQueryable<TE>>.
Let's say I have classes Country and CountryModel.
class CountryModel
{
public string Name {get;set;}
}
class Country{
public string Name{get;set;}
}
class Repo{
public IEnumerable<TD> Get(
Func<IQueryable<TD>, IOrderedQueryable<TD>> orderBy = null)
{
IQueryable<TEntityModel> query = CurrentDbSet;
return orderBy(query).ToList(); // Convert orderBy to TE.
}
}
Using the above method I would pass CountryModel instance. But the query has to happen the entity type Country.
There might be some syntax errors. Apologies for that.
public class Repo
{
public IEnumerable<TD> Get<TD, TE>(Func<IQueryable<TD>, IOrderedQueryable<TD>> orderBy = null)
{
IQueryable<TD> query = new List<TE>().Select(t => Convert<TD, TE>(t)).AsQueryable();
return orderBy(query).AsEnumerable(); // Convert orderBy to TE.
}
public TD Convert<TD, TE>(TE input) where TD : class
{
return input as TD;
}
}
If your type TE can be casted to the type TD this should work.
You can define explicit or implicit operator in your CountryModel to convert from Country
You may find AutoMapper to be very useful.
public static class MappingExtensions
{
static MappingExtensions()
{
Mapper.CreateMap<CustomAlerts, Domain.Models.CustomAlerts>();
Mapper.CreateMap<Domain.Models.CustomAlerts, CustomAlerts>();
//add mappings for each set of objects here.
//Remember to map both ways(from x to y and from y to x)
}
//map single objects
public static TDestination Map<TSource, TDestination>(TSource item)
{
return Mapper.Map<TSource, TDestination>(item);
}
//map collections
public static IEnumerable<TDestination> Map<TSource, TDestination>(IEnumerable<TSource> item)
{
return Mapper.Map<IEnumerable<TSource>, IEnumerable<TDestination>>(item);
}
}
Then in my code, I can do the following:
var TodayDate = DateTime.Now.AddDays(1);
var Alerts = DB.CustomAlerts
.Where(x => x.EndDate >= TodayDate)
.OrderByDescending(x => x.DateCreated)
.Skip(((id - 1) * 50))
.Take(50);
return Mapping.MappingExtensions.Map<CustomAlert,Models.CustomAlert>(Alerts).ToList();
Using AutoMapper and wrapping your Func with Expression should help you:
// Initialize AutoMapper
Mapper.Initialize(cfg =>
{
cfg.CreateMap<TE, TD>();
cfg.CreateMap<TD, TE>();
});
public class Repo
{
// Wrap Func with Expression
public IEnumerable<TD> Get(Expression<Func<IQueryable<TD>, IOrderedQueryable<TD>>> orderBy = null)
{
var orderByExpr = Mapper.Map<Expression<Func<IQueryable<TE>, IOrderedQueryable<TE>>>>(orderBy);
IQueryable<TE> query = CurrentDbSet;
// Compile expression and execute as function
var items = orderByExpr.Compile()(query).ToList();
// Map back to list of TD
return Mapper.Map<IEnumerable<TD>>(items);
}
}
I am working on a simple mapping EntityFramework <> DTO's , it's working perfecly excepto for the deferred execution , I have the following code :
public abstract class Assembler<TDto, TEntity> : IAssembler<TDto, TEntity>
where TEntity : EntityBase , new ()
where TDto : DtoBase, new ()
{
public abstract TDto Assemble(TEntity domainEntity);
public abstract TEntity Assemble(TEntity entity, TDto dto);
public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
{
List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
foreach (TEntity domainEntity in domainEntityList)
{
dtos.Add(Assemble(domainEntity));
}
return dtos.AsQueryable();
}
public virtual IQueryable<TEntity> Assemble(IQueryable<TDto> dtoList)
{
List<TEntity> domainEntities = Activator.CreateInstance<List<TEntity>>();
foreach (TDto dto in dtoList)
{
domainEntities.Add(Assemble(null, dto));
}
return domainEntities.AsQueryable();
}
}
Sample Assembler :
public partial class BlogEntryAssembler : Assembler<BlogEntryDto, BlogEntry>, IBlogEntryAssembler
{
public override BlogEntry Assemble(BlogEntry entity, BlogEntryDto dto)
{
if (entity == null)
{
entity = new BlogEntry();
}
/*
entity.Id = dto.Id;
entity.Created = dto.Created;
entity.Modified = dto.Modified;
entity.Header = dto.Header;
*/
base.MapPrimitiveProperties(entity, dto);
this.OnEntityAssembled(entity);
return entity;
}
public override BlogEntryDto Assemble(BlogEntry entity)
{
BlogEntryDto dto = new BlogEntryDto();
//dto.Id = entity.Id;
//dto.Modified = entity.Modified;
//dto.Created = entity.Created;
//dto.Header = entity.Header;
base.MapPrimitiveProperties(dto, entity);
dto.CategoryName = entity.Category.Name;
dto.AuthorUsername = entity.User.Username;
dto.AuthorFirstName = entity.User.FirstName;
dto.AuthorLastName = entity.User.LastName;
dto.TagNames = entity.Tags.Select(t => t.Name)
.ToArray();
dto.TagIds = entity.Tags.Select(t => t.Id)
.ToArray();
dto.VotedUpUsernames = entity.BlogEntryVotes.Where(v => v.Vote > 0)
.Select(t => t.User.Username)
.ToArray();
dto.VotedDownUsernames = entity.BlogEntryVotes.Where(v => v.Vote < 0)
.Select(t => t.User.Username)
.ToArray();
// Unmapped
dto.FileCount = entity.BlogEntryFiles.Count();
dto.CommentCount = entity.BlogEntryComments.Count();
dto.VisitCount = entity.BlogEntryVisits.Count();
dto.VoteCount = entity.BlogEntryVotes.Count();
dto.VoteUpCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(1));
dto.VoteDownCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(-1));
dto.VotePuntuation = entity.BlogEntryVotes.Sum(v => v.Vote);
dto.Published = entity.Visible && entity.PublishDate <= DateTime.Now;
this.OnDTOAssembled(dto);
return dto;
}
}
my service class :
public virtual PagedResult<BlogEntryDto> GetAll(bool includeInvisibleEntries, string tag, string search, string category, Paging paging)
{
var entries = this.Repository.GetQuery()
.Include(b => b.Tags)
.Include(b => b.User)
.Include(b => b.Category)
.Include(b => b.BlogEntryFiles)
.Include(b => b.BlogEntryComments)
.Include(b => b.BlogEntryPingbacks)
.Include(b => b.BlogEntryVisits)
.Include(b => b.BlogEntryVotes)
.Include(b => b.BlogEntryImages)
.AsNoTracking();
if (!includeInvisibleEntries)
{
entries = entries.Where(e => e.Visible);
}
if (!string.IsNullOrEmpty(category))
{
entries = entries.Where(e => e.Category.Name.Equals(category, StringComparison.OrdinalIgnoreCase));
}
if (!string.IsNullOrEmpty(tag))
{
entries = entries.Where(e => e.Tags.Count(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase)) > 0);
}
if (!string.IsNullOrEmpty(search))
{
foreach (var item in search.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
entries = entries.Where(e => e.Header.Contains(item));
}
}
return this.Assembler.Assemble(entries).GetPagedResult(paging);
}
When I call the GetAll method it returns and converts all the entities in the table to Dto's and only then it pages the resulting collection, of course that's not what I was expecting.. I would like to execute the code inside my Assemble method once the paging has been done, any idea?
PS : I know I could use Automapper, just trying to learn the internals.
In your assembler you add the projected DTOs to a List<TDto>. That's detrimental in two ways. First, it is forced execution, because the list is filled and then returned. Second, at that moment you switch from LINQ to Entities to LINQ to objects and there is no way back. You can convert the list to IQueryable again by AsQueryable, but that does not re-inject the EF query provider. In fact, the conversion is useless.
That's why AutoMapper's ProjectTo<T> statement is so cool. It transfers expressions that come after the To all the way back to the original IQueryable and, hence, its query provider. If these expressions contain paging statements (Skip/Take) these will be translated into SQL. So I think that you'll quickly come to the conclusion you better use AutoMapper after all.
public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
{
List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
foreach (TEntity domainEntity in domainEntityList)
{
dtos.Add(Assemble(domainEntity));
}
return dtos.AsQueryable();
}
The foreach loop in the above code is the point at which you're executing the query on the database server.
As you can see from this line:
return this.Assembler.Assemble(entries).GetPagedResult(paging);
This method is getting called before GetPagedResult(paging).. so that is the reason paging happens on the full result set.
You should understand that enumerating a query (foreach) requires the query to run. Your foreach loop processes each and every record returned by that query.. it's too late at this point for a Paging method to do anything to stop it!
I am developing a small framework to access the database. I want to add a feature that makes a query using a lambda expression. How do I do this?
public class TestModel
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Repository<T>
{
// do something.
}
For example:
var repo = new Repository<TestModel>();
var query = repo.AsQueryable().Where(x => x.Name == "test");
// This query must be like this:
// SELECT * FROM testmodel WHERE name = 'test'
var list = query.ToDataSet();
// When I call ToDataSet(), it will get the dataset after running the made query.
Go on and create a LINQ Provider (I am sure you don't want to do this, anyway).
It's a lot of work, so maybe you just want to use NHibernate or Entity Framework or something like that.
If your queries are rather simple, maybe you don't need a full blown LINQ Provider. Have a look at Expression Trees (which are used by LINQ Providers).
You can hack something like this:
public static class QueryExtensions
{
public static IEnumerable<TSource> Where<TSource>(this Repo<TSource> source, Expression<Func<TSource, bool>> predicate)
{
// hacks all the way
dynamic operation = predicate.Body;
dynamic left = operation.Left;
dynamic right = operation.Right;
var ops = new Dictionary<ExpressionType, String>();
ops.Add(ExpressionType.Equal, "=");
ops.Add(ExpressionType.GreaterThan, ">");
// add all required operations here
// Instead of SELECT *, select all required fields, since you know the type
var q = String.Format("SELECT * FROM {0} WHERE {1} {2} {3}", typeof(TSource), left.Member.Name, ops[operation.NodeType], right.Value);
return source.RunQuery(q);
}
}
public class Repo<T>
{
internal IEnumerable<T> RunQuery(string query)
{
return new List<T>(); // run query here...
}
}
public class TestModel
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var repo = new Repo<TestModel>();
var result = repo.Where(e => e.Name == "test");
var result2 = repo.Where(e => e.Id > 200);
}
}
Please, don't use this as it is. This is just a quick and dirty example how expression trees can be analyzed to create SQL statements.
Why not just use Linq2Sql, NHibernate or EntityFramework...
if you want to do things like
db.Employee
.Where(e => e.Title == "Spectre")
.Set(e => e.Title, "Commander")
.Update();
or
db
.Into(db.Employee)
.Value(e => e.FirstName, "John")
.Value(e => e.LastName, "Shepard")
.Value(e => e.Title, "Spectre")
.Value(e => e.HireDate, () => Sql.CurrentTimestamp)
.Insert();
or
db.Employee
.Where(e => e.Title == "Spectre")
.Delete();
Then check out this, BLToolkit
You might want to look at http://iqtoolkit.codeplex.com/ Which is very complex and i dont recommend you to build something from scratch.
I just wrote something close to dkons's answer I will add it anyway. Just using fluent interface nothing more.
public class Query<T> where T : class
{
private Dictionary<string, string> _dictionary;
public Query()
{
_dictionary = new Dictionary<string, string>();
}
public Query<T> Eq(Expression<Func<T, string>> property)
{
AddOperator("Eq", property.Name);
return this;
}
public Query<T> StartsWith(Expression<Func<T, string>> property)
{
AddOperator("Sw", property.Name);
return this;
}
public Query<T> Like(Expression<Func<T, string>> property)
{
AddOperator("Like", property.Name);
return this;
}
private void AddOperator(string opName, string prop)
{
_dictionary.Add(opName,prop);
}
public void Run(T t )
{
//Extract props of T by reflection and Build query
}
}
Lets say you have a model like
class Model
{
public string Surname{ get; set; }
public string Name{ get; set; }
}
You can use this as :
static void Main(string[] args)
{
Model m = new Model() {Name = "n", Surname = "s"};
var q = new Query<Model>();
q.Eq(x => x.Name).Like(x=>x.Surname).Run(m);
}