LINQ query to exclude overridden values? - c#

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();

Related

Linq where clause doesn't work in Entity Framework

This code doesn't work (returns null):
var result = context.Data
.Select(x => x)
.Where(x => x.ID == 1)
.FirstOrDefault();
But this:
var result = context.Data.Take(1);
works.
My question is why while I am using EF and the context.Data returns an IEnumerable<Data> the first code doesn't work? (And yes, the data contains element with ID equals 1)
This is a question that has really nothing to do with Entity Framework but the nature of how LINQ works with collections with it's extension methods. Let do a simple example in a console application in C#:
class Program
{
public class Faker
{
public int Id { get; set; }
public string Name { get; set; }
public Faker(int id, string name)
{
Id = id;
Name = name;
}
}
static void Main(string[] args)
{
var ls = new List<Faker>
{
new Faker(1, "A"),
new Faker(2, "B")
};
Faker singleItem = ls.FirstOrDefault(x => x.Id == 1);
IEnumerable<Faker> collectionWithSingleItem = ls.Where(x => x.Id == 1);
Console.ReadLine();
}
}
When I pause under 'Locals' I see the variables populated as such:
The simple answer is : it should work. Although your line could be optimized to :
var v = context.Data.FirstOrDefault(x => x.ID == 1);
So, basically there is no ID == 1 in your database, or you misspelled something.
If you wanted a IEnumerable<T> type then :
var v = context.Data.Where(x => x.ID == 1);
But I'd rather use list :
var v = context.Data.Where(x => x.ID == 1).ToList();

Enumeration Type error - Unable to create a constant C#

What I am trying to do is that, my PackageModel doesn't have an item Account. So I created a ViewModel (PackageViewModel) So I can add an Account item.
I am getting the following error. Can someone help me sort it ?
Additional information: Unable to create a constant value of type
'MyPROJ.Models.Account '. Only primitive types or enumeration types
are supported in this context.
Model is follows:
public class PackageModel
{
public int ID { get; set; }
}
VIEWMODEL is as follows:
public class PackageViewModel
{
public int ID { get; set; }
public Account acc {get; set;}
}
From my controller I am doing the following:
Account a = new db.Account.Find(currentLoggedInUser);
var xxx = db.PackageModel.Where(y => y.ID== 1)
.Select(x => new PackageViewModel()
{
ID= x.ID,
acc = a
});
return (xxx.ToList());
VIEW
<div class="myall">
#Html.Partial("_SomePage", #Model.First().Account)
...
</div>
You can't use closure-captured variables in a Linq-to-Entities query which are non-trivial types.
Account a = db.Account.Find( currentLoggedInUser );
var xxx = db.PackageModel.Where(y => y.ID== 1)
.Select(x => new PackageViewModel()
{
ID= x.ID,
acc = a // <-- you're capturing `a` which is an `Account`, a class type, not a trivial value or object
});
Change it so that the the Select happens in Linq-to-Objects after the Linq-to-Entities query has completed, by using either ToList or AsEnumerable:
Account a = db.Account.Find( currentLoggedInUser );
var xxx = db.PackageModel
.Where( p => p.ID == 1 ) // <-- this part is Linq-to-Entities
.AsEnumerable() // <-- this causes the rest of the Linq construct to be evaluated in Linq-to-Objects
.Select( p => new PackageViewModel() // <-- this part is Linq-to-Objects
{
ID = x.ID,
acc = a // <-- now you can capture non-trivial values
});
...however I notice you're using Where but with a predicate that will return a single element (assuming ID is unique), you should use SingleOrDefault instead:
Account acc = db.Account.Find( currentLoggedInUser );
Package package = db.PackageModel.SingleOrDefault( p => p.ID == 1 );
if( package == null ) throw new InvalidOperationException("Package not found");
PackageViewModel vm = new PackageViewModel() { ID = package.ID, Acc = acc };
return this.View( vm );

Modifying Expression<Func<T, bool>>

I haven't worked with expressions that much so I can't say if my intention makes any sense at all. I have looked around the interwebs and SO to no avail.
Say I have a method like so
public async Task<T> GetFirstWhereAsync(Expression<Func<T, bool>> expression)
{
// this would be a call to 3rd party dependency
return await SomeDataProvider
.Connection
.Table<T>()
.Where(expression)
.FirstOrDefaultAsync();
}
And from my-other-code I could be calling this e.g.
private async Task<User> GetUser(Credentials credentials)
{
return await SomeDataSource
.GetFirstWhereAsync(u => u.UserName.Equals(credentials.UserName));
}
So I'd be receiving first User from my SomeDataProvider that matches the given expression.
My actual question is how would I go about modifying GetFirstWhereAsync so that it would apply some SecretSauce to any expression passed to it? I could do this in caller(s) but that would be ugly and not much fun.
So if I pass in expressions like
u => u.UserName.Equals(credentials.UserName);
p => p.productId == 1;
I'd like these to be modified to
u => u.UserName.Equals(SecretSauce.Apply(credentials.UserName));
p => p.productId == SecrectSauce.Apply(1);
You can modify the expression inside method, but it's a bit complicated and you will need to do it case-by-case basis.
Example below will deal with modification from x => x.Id == 1 to x => x.Id == SecretSauce.Apply(1)
Class
class User
{
public int Id { get; set; }
public string Name { get; set;}
public override string ToString()
{
return $"{Id}: {Name}";
}
}
Sauce
class SquareSauce
{
public static int Apply(int input)
{
// square the number
return input * input;
}
}
Data
User[] user = new[]
{
new User{Id = 1, Name = "One"},
new User{Id = 4, Name = "Four"},
new User{Id = 9, Name = "Nine"}
};
Method
User GetFirstWhere(Expression<Func<User, bool>> predicate)
{
//get expression body
var body = predicate.Body;
//get if body is logical binary (a == b)
if (body.NodeType == ExpressionType.Equal)
{
var b2 = ((BinaryExpression)body);
var rightOp = b2.Right;
// Sauce you want to apply
var methInfo = typeof(SquareSauce).GetMethod("Apply");
// Apply sauce to the right operand
var sauceExpr = Expression.Call(methInfo, rightOp);
// reconstruct equals expression with right operand replaced
// with "sauced" one
body = Expression.Equal(b2.Left, sauceExpr);
// reconstruct lambda expression with new body
predicate = Expression.Lambda<Func<User, bool>>(body, predicate.Parameters);
}
/*
deals with more expression type here using else if
*/
else
{
throw new ArgumentException("predicate invalid");
}
return user
.AsQueryable()
.Where(predicate)
.FirstOrDefault();
}
Usage
Console.WriteLine(GetFirstWhere(x => x.Id == 2).ToString());
The method will turn x => x.Id == 2 to x => x.Id == SquareSauce.Apply(2) and will produce:
4: Four

How to add if statement to a LINQ query in C#?

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();

Is there a way to search a List<T> using a partially populated object?

Would like to be able to populate any properties of an object and search a collection for objects that match the given properties.
class Program
{
static List<Marble> marbles = new List<Marble> {
new Marble {Color = "Red", Size = 3},
new Marble {Color = "Green", Size = 4},
new Marble {Color = "Black", Size = 6}
};
static void Main()
{
var search1 = new Marble { Color = "Green" };
var search2 = new Marble { Size = 6 };
var results = SearchMarbles(search1);
}
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
var results = from marble in marbles
//where ???
//Search for marbles with whatever property matches the populated properties of the parameter
//In this example it would return just the 'Green' marble
select marble;
return results;
}
public class Marble
{
public string Color { get; set; }
public int Size { get; set; }
}
}
Admittedly, it is interesting and take me time. First, you need to get all properties of search object which have value different with default value, this method is generic using reflection:
var properties = typeof (Marble).GetProperties().Where(p =>
{
var pType = p.PropertyType;
var defaultValue = pType.IsValueType
? Activator.CreateInstance(pType) : null;
var recentValue = p.GetValue(search);
return !recentValue.Equals(defaultValue);
});
Then you can use LINQ All to filter:
var results = marbles.Where(m =>
properties.All(p =>
typeof (Marble).GetProperty(p.Name)
.GetValue(m) == p.GetValue(search)));
P.s: This code has been tested
I am going to propose the generic solution which will work with any number of properties and with any object. It will also be usable in Linq-To-Sql context - it will translate well to sql.
First, start by defining function which will test if the given value is to be treated as a non-set, e.g:
static public bool IsDefault(object o)
{
return o == null || o.GetType().IsValueType && Activator.CreateInstance(o.GetType()).Equals(o);
}
Then, we will have a function which constructs a Lambda expression with test against the values of all set properties in search object:
static public Expression<Func<T, bool>> GetComparison<T>(T search)
{
var param = Expression.Parameter(typeof(T), "t");
var props = from p in typeof(T).GetProperties()
where p.CanRead && !IsDefault(p.GetValue(search, null))
select Expression.Equal(
Expression.Property(param, p.Name),
Expression.Constant(p.GetValue(search, null))
);
var expr = props.Aggregate((a, b) => Expression.AndAlso(a, b));
var lambda = Expression.Lambda<Func<T, bool>>(expr, param);
return lambda;
}
We can use it on any IQueryable:
public static IEnumerable<Marble> SearchMarbles (Marble search)
{
var results = marbles.AsQueryable().Where(GetComparison(search));
return results.AsEnumerable();
}
You can use a separate Filter class like this:
class Filter
{
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
public bool Matches(Marble m)
{
var T = typeof(Marble);
var prop = T.GetProperty(PropertyName);
var value = prop.GetValue(m);
return value.Equals(PropertyValue);
}
}
You can use this Filter as follows:
var filters = new List<Filter>();
filters.Add(new Filter() { PropertyName = "Color", PropertyValue = "Green" });
//this is essentially the content of SearchMarbles()
var result = marbles.Where(m => filters.All(f => f.Matches(m)));
foreach (var r in result)
{
Console.WriteLine(r.Color + ", " + r.Size);
}
You could use DependencyProperties to get rid of typing the property name.
Assuming a property is unpopulated if it has the default value (i.e. Color == null and Size == 0):
var results = from marble in marbles
where (marble.Color == search.Color || search.Color == null)
&& (marble.Size == search.Size || search.Size == 0)
select marble;
You could override equals in your Marbles class
public override bool Equals(object obj)
{
var other = obj as Marble;
if (null == other) return false;
return other.Color == this.color && other.size == this.size; // (etc for your other porperties
}
and then you could search by
return marbles.Where(m => search == m);
Using reflection, this method will work on all types, regardless of how many or what type of properties they contain.
Will skip any properties that are not filled out (null for ref type, default value for value type). If it finds two properties that are filled out that do not match returns false. If all filled-out properties are equal returns true.
IsPartialMatch(object m1, object m2)
{
PropertyInfo[] properties = m1.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
object v1 = property.GetValue(m1, null);
object v2 = property.GetValue(m2, null);
object defaultValue = GetDefault(property.PropertyType);
if (v1.Equals(defaultValue) continue;
if (v2.Equals(defaultVAlue) continue;
if (!v1.Equals(v2)) return false;
}
return true;
}
To apply it to your example
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
return marbles.Where(m => IsPartialMatch(m, search))
}
GetDefault() is method from this post, Programmatic equivalent of default(Type)
If you want to avoid targeting specific properties, you could use reflection. Start by defining a function that returns the default value of a type (see here for a simple solution, and here for something more elaborate).
Then, you can write a method on the Marble class, which takes an instance of Marble as a filter:
public bool MatchesSearch(Marble search) {
var t = typeof(Marble);
return !(
from prp in t.GetProperties()
//get the value from the search instance
let searchValue = prp.GetValue(search, null)
//check if the search value differs from the default
where searchValue != GetDefaultValue(prp.PropertyType) &&
//and if it differs from the current instance
searchValue != prp.GetValue(this, null)
select prp
).Any();
}
Then, the SearchMarbles becomes:
public static IEnumerable<Marble> SearchMarbles(Marble search) {
return
from marble in marbles
where marble.MatchesSearch(search)
select marble;
}

Categories