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();
Related
I am looking for a way to dynamically build an expression, based upon the method parameters.
This is the code snippet from my service method, where I would like to build a predicate expression.
public async Task<Account> GetCustomerAccountsAsync(Parameters params)
{
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
...
}
GetWhereAsync is a method from the generic repository that looks like:
public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Context.Set<TEntity>().Where(predicate).ToListAsync();
}
And Parameters class:
public class Parameters
{
public string CustomerId { get; set; }
public string AccountId { get; set; }
public string ProductId { get; set; }
public string CurrencyId { get; set; }
}
What I would like to implement, is that every Parameter object property that is not null,
to be added to expression predicate as a condition.
For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression
that would have functionality same as the following predicate:
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);
Appreciate any help.
You don't need to use Expressions to build something dynamically here. You can do something like this:
_unitOfWork.Accounts.Where(a =>
(params.CustomerId == null || a.CustomerId == params.CustomerId) &&
(params.AccountId == null || a.AccountId == params.AccountId) &&
(params.ProductId == null || a.ProductId == params.ProductId) &&
(params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);
This is how I've done queries before for a search form with multiple optional search parameters.
I'm currently working a lot with Expressions so I think I can help you.
I just built a custom code for you.
The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.
The code filters string Properties and use it to create the Predicate.
public Func<Account, bool> GetPredicate(Parameters parameters)
{
var stringProperties = typeof(Parameters)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(string));
var parameterExpression = Expression.Parameter(typeof(Account));
var notNullPropertyNameToValue = new Dictionary<string, string>();
BinaryExpression conditionExpression = null;
foreach (var stringProperty in stringProperties)
{
var propertyValue = (string)stringProperty.GetValue(parameters);
if (propertyValue != null)
{
var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);
if (conditionExpression == null)
{
conditionExpression = propertyTestExpression;
}
else
{
conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
}
}
}
//Just return a function that includes all members if no parameter is defined.
if (conditionExpression == null)
{
return (x) => true;
}
var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
return lambdaExpression.Compile();
}
It returns a typed predicate that you can use in Linq for example.
See this example :
void Main()
{
var customers = new List<Account>()
{
new Account()
{
CustomerId = "1",
},
new Account()
{
CustomerId = "2",
}
};
var predicate = GetPredicate(new Parameters() { CustomerId = "1" });
customers.Where(predicate);
}
If any help is needed, feel free to ask !
I am using ASP.NET MVC with EF 6.
I have a stock page which shows all the information on stock items. Now I want to filter records too.
In picture below I have 3 options. I might filter by each option, one at a time or by combination of two or with all three.
I was thinking of writing linq query for each and every options selected. But this wouldn't be possible if filter option increases.Is there is any better way to this.
Thanks!
This is what I did in my controller.(currently dropdown has two options, excluding : " -- select one -- ")
public ActionResult StockLevel(string option, string batch, string name)
{
if (option != "0" && batch == "" && name == "")
{
if(option == "BelowMin")
{
List<Stock> stk = (from s in db.Stocks
where s.Qty < s.Item.AlertQty
select s).ToList();
return View(stk);
}
else
{
List<Stock> stk = (from s in db.Stocks
where s.Qty == s.InitialQty
select s).ToList();
return View(stk);
}
}
if (option == "0" && batch != "" && name == "")
{
List<Stock> stk = (from s in db.Stocks
where s.BatchNo == batch
select s).ToList();
return View(stk);
}
if (option == "0" && batch == "" && name != "")
{
List<Stock> stk = (from s in db.Stocks
where s.Item.Name.StartsWith(""+name+"")
select s).ToList();
return View(stk);
}
return View(db.Stocks.ToList());
}
I recommend you separate concerns and use an approach that the code in your controller be like this, simple, beautiful and extensible:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Benefits:
You can put anything you need in your ProductSearchModel based on your requirements.
You can write any logic in GetProducts based on requirements. There is no limitation.
If you add a new field or option to search, your action and controller will remain untouched.
If the logic of your search changes, your action and controller will remain untouched.
You can reuse logic of search wherever you need to search on products, in controllers or even in other business logic.
Having such ProductSearchModel, you can use it as model of ProductSearch partial view and you can apply DataAnnotations to it to enhance the model validation and help UI to render it using Display or other attributes.
You can add other business logic related to your product in that business logic class.
Following this way you can have a more organized application.
Sample Implementation:
Suppose you have a Product class:
public class Product
{
public int Id { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
You can create a ProductSearchModel class and put some fields you want to search based on them:
public class ProductSearchModel
{
public int? Id { get; set; }
public int? PriceFrom { get; set; }
public int? PriceTo { get; set; }
public string Name { get; set; }
}
Then you can put your search logic in ProductBusinessLogic class this way:
public class ProductBusinessLogic
{
private YourDbContext Context;
public ProductBusinessLogic()
{
Context = new YourDbContext();
}
public IQueryable<Product> GetProducts(ProductSearchModel searchModel)
{
var result = Context.Products.AsQueryable();
if (searchModel != null)
{
if (searchModel.Id.HasValue)
result = result.Where(x => x.Id == searchModel.Id);
if (!string.IsNullOrEmpty(searchModel.Name))
result = result.Where(x => x.Name.Contains(searchModel.Name));
if (searchModel.PriceFrom.HasValue)
result = result.Where(x => x.Price >= searchModel.PriceFrom);
if (searchModel.PriceTo.HasValue)
result = result.Where(x => x.Price <= searchModel.PriceTo);
}
return result;
}
}
Then in your ProductController you can use this way:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Important Note:
In a real world implementation, please consider implementing a suitable Dispose pattern for your business class to dispose db context when needed. For more information take a look at Implementing a Dispose method or Dispose Pattern.
Conditional filtering
.ToList(), .First(), .Count() and a few other methods execute the final LINQ query. But before it is executed you can apply filters just like that:
var stocks = context.Stocks.AsQueryable();
if (batchNumber != null) stocks = stocks.Where(s => s.Number = batchNumber);
if (name != null) stocks = stocks.Where(s => s.Name.StartsWith(name));
var result = stocks.ToList(); // execute query
WhereIf LINQ Extension
Simple WhereIf can significantly simplify code:
var result = db.Stocks
.WhereIf(batchNumber != null, s => s.Number == batchNumber)
.WhereIf(name != null, s => s.Name.StartsWith(name))
.ToList();
WhereIf implementation. It's a simple extension method for IQueryable:
public static class CollectionExtensions
{
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
}
Non-WhereIf LINQ way (Recommended)
WhereIf provides more declarative way, if you don't want to use extensions you can just filter like that:
var result = context.Stocks
.Where(batchNumber == null || stock.Number == batchNumber)
.Where(name == null || s => s.Name.StartsWith(name))
.ToList();
It gives an exact same effect as WhereIf and it will work faster as runtime will need to build just one ExpressionTree instead of building multiple trees and merging them.
I've written some extensions to make this easier. https://www.nuget.org/packages/LinqConditionalExtensions/
It's not reinventing the wheel. Some of the extensions have already been recommended. You could rewrite your logic as follows.
var results = db.Stocks
.If(option != "0", stocks => stocks
.IfChain(option == "BelowMin", optionStocks => optionStocks
.Where(stock => stock.Qty < stock.Item.AlertQty))
.Else(optionStocks => optionStocks
.Where(stock => stock.Qty == stock.InitialQty)))
.WhereIf(!string.IsNullOrWhiteSpace(batch), stock => stock.BatchNo == batch)
.WhereIf(!string.IsNullOrWhiteSpace(name), stock => stock.Item.Name.StartsWith("" + name + ""))
.ToList();
return results;
Basically, the initial If() method will apply the passed if-chain if the condition is true. The IfChain() is your nested if-else statement. IfChain() allows you to chain multiple IfElse() and end with an Else().
The WhereIf() will just conditionally apply your where clause if the condition is true.
If you are interested in the library, https://github.com/xKloc/LinqConditionalExtensions has a readme.
public ActionResult Index(string searchid)
{
var personTables = db.PersonTables.Where(o => o.Name.StartsWith(searchid) )|| o.CombanyTable.ComName.StartsWith(searchid) ).Include(k => k.CombanyTable);
return View(personTables.ToList());
}
I have a collection of Employee class and employee class has few properties like departement,manager name,payscale,designation etc.Now in my webapi, i have a search method in which i am searching a string across all fields of webapi
like if I search Peter it'll search in all fields(departement,manager_name,payscale,designation) of all employees.For this i am using below:-
public IEnumerable<Employee> Search(string searchstr)
{
if (repository != null)
{
var query =
from employees in repository.GetEmployees()
where
(employees.departement != null && employees.departement.Contains(searchstr)) ||
(employees.payscale != null && employees.payscale.Contains(searchstr))
(movie.designation != null && movie.designation.Contains(searchstr)) )
select employees;
return query.AsEnumerable().OrderBy(c => c.employeeid);
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
Though i am getting desired result,i have not to use that query.Is there any other way to rewrite same query?
As stated by Noctis, using reflection results in a heavy task for the .NET runtime.
Here is some example code that loops trough all properties of a class and searchs a string concidence. It uses reflection ;)
Any questions, leave a comment asking!
The code in the entry point of the APP:
[STAThread]
private static void Main(string[] args)
{
var person1 = new Person {Name = "The first name", Address = "The first address"};
var person2 = new Person {Name = "The second name", Address = "The second address"};
var results = SearchStringTroughAllProperties("second", new List<Person> {person1, person2});
}
The Person class:
class Person
{
public string Name { get; set; }
public string Address { get; set; }
}
And the SearchStringTroughAllProperties method:
private static IEnumerable<Person> SearchStringTroughAllProperties(string stringToSearch,
IEnumerable<Person> persons)
{
var properties =
typeof (Person).GetProperties()
.Where(x => x.CanRead && x.PropertyType == typeof (string))
.Select(x => x.GetMethod)
.Where(x => !x.IsStatic)
.ToList();
return persons.Where(person =>
properties.Select(property => (string) property.Invoke(person, null) ?? string.Empty)
.Any(propertyValueInInstance => propertyValueInInstance.Contains(stringToSearch)));
}
Notice that:
It searchs in properties, not in fields
It only searchs on properties that can be read (have a get defined)
It only searchs on properties of type string
It only searchs on properties that aren't static members of the class
EDIT:
For searching the string coincidence in a string or string[] property, change SearchStringTroughAllProperties method to this (it gets longer!):
static IEnumerable<Person> SearchStringTroughAllProperties(string stringToSearch, IEnumerable<Person> persons)
{
var properties =
typeof (Person).GetProperties()
.Where(x => x.CanRead && (x.PropertyType == typeof (string) || x.PropertyType == typeof(string[])))
.Select(x => x.GetMethod)
.Where(x => !x.IsStatic)
.ToList();
foreach (var person in persons)
{
foreach (var property in properties)
{
bool yieldReturned = false;
switch (property.ReturnType.ToString())
{
case "System.String":
var propertyValueStr = (string) property.Invoke(person, null) ?? string.Empty;
if (propertyValueStr.Contains(stringToSearch))
{
yield return person;
yieldReturned = true;
}
break;
case "System.String[]":
var propertyValueStrArr = (string[]) property.Invoke(person, null);
if (propertyValueStrArr != null && propertyValueStrArr.Any(x => x.Contains(stringToSearch)))
{
yield return person;
yieldReturned = true;
}
break;
}
if (yieldReturned)
{
break;
}
}
}
}
Even though work, it feels a bit dirty. I would consider maybe using reflection to get the properties of the class, and then dynamically search them.
The benefit would be: if you add a new property tomorrow, there's nothing else you need to change.
The disadvantage would be: probably not as performant since reflection is much slower than simply searching for things you know exist.
Having said that, i'm sure there are some other nifty advanced linq tricks that maybe others can point out.
Accessing Attributes by Using Reflection (C# and Visual Basic)
I thought I have some handy code but I don't. I wouldn't like to write it of the top of my head, because it probably won't compile (you need to get the syntax right).
Have a look at the above link :)
I have some tables as follows:
ImageSettingOverrides
TechniqueSettings
SettingKeyValues
From TechniqueSettings table:
BAZ-FOO setting (SettingKeyId: 7) is an override to the BAZ-Default (SettingKeyId: 4) setting.
Example of expected return from query grouped by Override value:
I want to compile a list of SettingKeyValues given technique BAZ and override FOO that excludes the overridden BAZ-Default settings and includes non-overridden BAZ-Default settings.
I currently have a LINQ query that groups setting-key values based on Default/Override values:
var techniqueSettings = _dataRepository.All<TechniqueSetting>()
.Where(s => s.Technique.Equals(TechniqueName, StringComparison.InvariantCultureIgnoreCase))
// group settings by: e.g. Default | FOO
.GroupBy(s => s.Override);
From there I determine if the user is querying for just the defaults or the defaults with overrides:
var techniqueGroups = techniqueSettings.ToArray();
if (OverridesName.Equals("Default", StringComparison.InvariantCultureIgnoreCase)) {
// get the default group and return as queryable
techniqueSettings = techniqueGroups
.Where(grp => grp.Key.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
.AsQueryable();
} else {
// get the overrides group - IGrouping<string, TechniqueSetting>
var overridesGroup = techniqueGroups
.Where(grp => !grp.Key.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
.First();
var defaultGroup = techniqueGroups
.Where(grp => grp.Key.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
// we know what is in the overrides, so exlude them from being selected here
// how to exlude overridden defaults???
.First();
}
In addition, I can't help but think there must be an easier - less clumsy - LINQ query using JOIN (maybe ???).
NOTE: Using EntityFramework 6.x
__
UPDATE:
I found Aggregate seems to simplify somewhat but still required an anonymous method.
var defaultGroup = techniqueGroups
.Where(grp => grp.Key.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
.Aggregate(overridesGroup,
(overrides, defaults) => {
var settings = new List<TechniqueSetting>();
foreach (var setting in defaults) {
if (overrides.Any(o => o.SettingKey.Key == setting.SettingKey.Key)) {
continue;
}
settings.Add(setting);
}
return settings.GroupBy(s => s.Override).First();
},
setting => setting);
I haven't tried the Join yet per comment by #MarkoDevcic.
Can Except be used in this query?
Revised Answer
With values
int myImageId = 1;
string myOverride = "FOO";
string myTechnique = "BAZ";
results =
ImageId Override Value
1 FOO 1000
With Values
int myImageId = 1;
string myOverride = "Default";
string myTechnique = "BAZ";
results =
ImageId Override Value
1 Default 10000
void Main()
{
// Create Tables and Initialize values
// ***********************************
var techniqueSettings = new List<TechniqueSettings>();
techniqueSettings.Add(new TechniqueSettings { Id = 1, Override = "Default", SettingKeyId = 3, Technique="BAZ"});
techniqueSettings.Add(new TechniqueSettings { Id = 2, Override = "Default", SettingKeyId = 4, Technique="BAZ"});
techniqueSettings.Add(new TechniqueSettings { Id = 3, Override = "FOO", SettingKeyId = 7, Technique="BAZ"});
techniqueSettings.Add(new TechniqueSettings { Id = 4, Override = "FOO", SettingKeyId = 8, Technique="BAZ"});
var imageSettingOverrides = new List<ImageSettingOverrides>();
imageSettingOverrides.Add(new ImageSettingOverrides {SettingId = 1, ImageId=1, Override=null } );
imageSettingOverrides.Add(new ImageSettingOverrides {SettingId = 2, ImageId=1, Override="FOO" } );
imageSettingOverrides.Add(new ImageSettingOverrides {SettingId = 3, ImageId=1, Override="FOO" } );
var settingKeyValues = new List<SettingKeyValues>();
settingKeyValues.Add(new SettingKeyValues {Id = 4, Setting="Wait", Value=1000 } );
settingKeyValues.Add(new SettingKeyValues {Id = 7, Setting="Wait", Value=10000 } );
int myImageId = 1;
string myOverride = "FOO";
string myTechnique = "BAZ";
var results = from iso in imageSettingOverrides
join ts in techniqueSettings on iso.SettingId equals ts.Id
join skv in settingKeyValues on ts.SettingKeyId equals skv.Id
where iso.ImageId == myImageId &&
//iso.Override.Equals(myOverride,StringComparison.InvariantCultureIgnoreCase) &&
ts.Override.Equals(myOverride,StringComparison.InvariantCultureIgnoreCase) &&
ts.Technique.Equals(myTechnique, StringComparison.InvariantCultureIgnoreCase)
select new {
ImageId = iso.ImageId,
Override = ts.Override,
Value = skv.Value
};
results.Dump();
}
// Define other methods and classes here
public class ImageSettingOverrides
{
public int SettingId {get; set;}
public int ImageId {get; set;}
public string Override {get; set;}
}
public class TechniqueSettings
{
public int Id {get; set;}
public string Override {get; set;}
public int SettingKeyId {get; set;}
public string Technique { get; set;}
}
public class SettingKeyValues
{
public int Id {get; set;}
public String Setting {get; set;}
public int Value {get; set;}
}
I assume you expect each of SettingKeyValues in the result will have unique Setting value (it doesn't make sense to have two 'Wait' records with different numbers against them).
Here is query:
var result =
(
from ts in techniqueSettings
// For only selected technique
where ts.Technique.Equals("BAZ", StringComparison.InvariantCultureIgnoreCase)
// Join with SettingsKeyValues
join skv in settingKeyValues on ts.SettingKeyId equals skv.Id
// intermediate object
let item = new { ts, skv }
// Group by SettingKeyValues.Setting to have only one 'Wait' in output
group item by item.skv.Setting into itemGroup
// Order items inside each group accordingly to Override - non-Default take precedence
let firstSetting = itemGroup.OrderBy(i => i.ts.Override.Equals("Default") ? 1 : 0).First()
// Return only SettingKeyValue
select firstSetting.skv
)
.ToList();
I'm going to make some assumptions.
That if there is an ImageSettingOverrides the override also has to match the override passed in AKA FOO (that's the where iSettingsOverrides => iSettingsOverrides.Override == OverridesName in the join clause
You only want a distinct list of SettingKeyValues
TechniqueSetting.Id is the key and ImageSettingOverride.TechniqueSettingsId is the foreign key and that's how they are related
SettingKeyValue.Id is the key and TechniqueSetting.SettingKeyId is the foreign key and that's how they are related.
You don't have navigation properties and I have to do the join.
If I understand your classes and how they are related this should give you a list of SettingKeyValues. Since everything stays IQueryable it should execute on the server.
//I'm assuming these are your variables for each IQueryable
IQueryable<TechniqueSetting> techniqueSettings;
IQueryable<ImageSettingOverride> imageSettingOverrides;
IQueryable<SettingKeyValue> settingKeyValues;
var OverridesName = "FOO";
var TechniqueName = "BAZ";
IQueryable<TechniqueSetting> tSettings;
if (OverridesName.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
{
// Get a list of TechniqueSettings that have this name and are default
tSettings = techniqueSettings.Where(t => t.Override == OverridesName && t.Technique == TechniqueName);
}
else
{
// Get a list of TechniqueSettings Id that are overridden
// The ImageSettingOverrides have the same override
var overriddenIDs = techniqueSettings.Where(t => t.Technique == TechniqueName && t.Override == "Default")
.Join(
imageSettingOverrides.Where(
iSettingsOverrides =>
iSettingsOverrides.Override == OverridesName),
tSetting => tSetting.SettingKeyId,
iSettings => iSettings.TechniqueSettingsId,
(tSetting, iSettingsOverrides) => tSetting.Id);
// Get a list of techniqueSettings that match the override and TechniqueName but are not part of the overriden IDs
tSettings =
techniqueSettings.Where(
t =>
t.Technique == TechniqueName && !overriddenIDs.Contains(t.Id) &&
(t.Override == OverridesName || t.Override == "Default"));
}
// From expected results seems you just want techniqueSettings and that's what would be in techniqueSettings right now.
// If you want a list of SettingKeyValues (which is what is stated in the questions we just need to join them in now)
var settings = tSettings.Join(settingKeyValues, tSetting => tSetting.SettingKeyId,
sKeyValues => sKeyValues.Id, (tSetting, sKeyValues) => sKeyValues)
.Distinct();
I found Aggregate seems to simplify somewhat but still required an anonymous method.
var defaultGroup = techniqueGroups
.Where(grp => grp.Key.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
.Aggregate(overridesGroup,
(overrides, defaults) => {
var settings = new List<TechniqueSetting>();
foreach (var setting in defaults) {
if (overrides.Any(o => o.SettingKey.Key == setting.SettingKey.Key)) {
continue;
}
settings.Add(setting);
}
return settings.GroupBy(s => s.Override).First();
},
setting => setting);
Update:
I came up with a couple of extension methods that allows for exclusion of items and comparisons and replacements:
internal static IEnumerable<TSource> Exclude<TSource>(this IEnumerable<TSource> Source, Func<TSource, bool> Selector) {
foreach (var item in Source) {
if (!Selector(item)) {
yield return item;
}
}
}
internal static IEnumerable<TResult> ReplaceWith<TSource1, TSource2, TResult>(this IEnumerable<TSource1> Source1,
Func<TSource1, TResult> Source1Result,
IEnumerable<TSource2> Source2,
Func<TSource1, IEnumerable<TSource2>, TResult> Selector) {
foreach (var item in Source1) {
var replaceWith = Selector(item, Source2);
if (replaceWith == null) {
yield return Source1Result(item);
continue;
}
yield return replaceWith;
}
}
Exclude is fairly straightforward. For ReplaceWith usage:
var settings = _repository.Settings
.ReplaceWith(s => s.SettingKeyValue,
_repository.SettingOverrides.Where(o => o.OverrideName == overrideName),
(s, overrides) => overrides.Where(o => o.Setting == s)
.Select(o => o.SettingKeyValueOverride)
.FirstOrDefault())
.ToList();
I need to support a variable number of Orderby terms in a Linq (to Entity) statement. That is, my function will accept a list of properties on which the data should be order. The properties can have both ascending or descending sorts. What is the best way to handle constructing the Linq query?
Thanks!
You should be able to do something along these lines:
public IEnumerable<MyType> DoSomething(params Expression<Func<MyType,object>>[] properties)
{
var query = // create LINQ query that returns IQueryable<MyType>
query = query.OrderBy(properties.First());
foreach (var property in properties.Skip(1))
{
query = query.ThenBy(property);
}
}
…
var results = DoSomething(() => x.Age, () => x.Height, () => x.LastName);
You'd need to handle the case where fewer than 2 properties are specified.
Following on from Jay's answer, this can be made into a nice extension method:
public static class EnumerableExtensions
{
public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
params Expression<Func<T, object>>[] expressions)
{
if (expressions.Length == 1)
return enumerable.OrderBy(expressions[0].Compile());
var query = enumerable.OrderBy(expressions[0].Compile());
for (int i = 1; i < expressions.Length;i++)
{
query = query.ThenBy(expressions[i].Compile());
}
return query;
}
}
Usage becomes quite simple, given a test object:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
This is then possible:
var people = new Person[]
{
new Person() {Name = "John", Age = 40},
new Person() {Name = "John", Age = 20},
new Person() {Name = "Agnes", Age = 11}
};
foreach(var per in people.OrderByMany(x => x.Name, x => x.Age))
{
Console.WriteLine("{0} Age={1}",per.Name,per.Age);
}
Output:
Agnes Age=11
John Age=20
John Age=40
UPDATE
You could add another overload of the OrderByMany method to support SortOrder as well, although it gets clunky rather quickly. Personally I'd just go for the syntax
var query = from p
in people
order by Name, Age descending;
However, for the record, in C#4 at least, I would accomplish the overload using an enum & tuple.
public enum SortOrder
{
Ascending,
Descending
}
and the extra overload:
public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
params Tuple<Expression<Func<T, object>>,SortOrder>[] expressions)
{
var query = (expressions[0].Item2 == SortOrder.Ascending)
? enumerable.OrderBy(expressions[0].Item1.Compile())
: enumerable.OrderByDescending(expressions[0].Item1.Compile());
for (int i = 1; i < expressions.Length; i++)
{
query = expressions[i].Item2 == SortOrder.Ascending
? query.ThenBy(expressions[i].Item1.Compile())
: query.ThenByDescending(expressions[i].Item1.Compile());
}
return query;
}
Usage becomes clumsy and hard to read:
foreach (var per in people.OrderByMany(
new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Age, SortOrder.Descending),
new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Name, SortOrder.Ascending)))
{
Console.WriteLine("{0} Age={1}", per.Name, per.Age);
}
To sort by an arbitrary property, you need to build an expression tree to pass to OrderBy.
To sort by an arbitrary number of properties, you need to call ThenBy in a loop.
I like Jamiec's idea but I hate using Tuples because the syntax is ugly. Therefore I built a small class that encapsulates the Tuple and exposes getters for the Item1 and Item2 properties with better variable names.
Also notice that I used a default sort order of ascending so you only need to specify a SortOrder if you want to sort in descending order.
public class SortExpression<T>
{
private Tuple<Expression<Func<T, object>>, SortOrder> tuple;
public SortExpression( Expression<Func<T, object>> expression, SortOrder order =SortOrder.Ascending )
{
tuple = new Tuple<Expression<Func<T,object>>, SortOrder>(expression, order);
}
public Expression<Func<T, object>> Expression {
get { return tuple.Item1; }
}
public SortOrder Order {
get { return tuple.Item2; }
}
}
In my specific application, I have a repository base class which takes an IQueryable and converts it to a ObservableCollection. In that method I use the SortExpression class:
public ObservableCollection<T> GetCollection(params SortExpression<T>[] sortExpressions) {
var list = new ObservableCollection<T>();
var query = FindAll();
if (!sortExpressions.Any()) {
query.ToList().ForEach(list.Add);
return list;
}
var ordered = (sortExpressions[0].Order == SortOrder.Ascending)
? query.OrderBy(sortExpressions[0].Expression.Compile())
: query.OrderByDescending(sortExpressions[0].Expression.Compile());
for (var i = 1; i < sortExpressions.Length; i++) {
ordered = sortExpressions[i].Order == SortOrder.Ascending
? ordered.ThenBy(sortExpressions[i].Expression.Compile())
: ordered.ThenByDescending(sortExpressions[i].Expression.Compile());
}
ordered.ToList().ForEach(list.Add);
return list;
}
Here is the method in use:
var repository = new ContactRepository(UnitOfWork);
return repository.GetCollection(
new SortExpression<Contact>(x => x.FirstName),
new SortExpression<Contact>(x => x.LastName));