I need to transform my property (remove white space on a string) first before applying validation.
Specifically I want to check if a string is part of a enum but the string may contain whitespaces (which enums don't allow far as I know)
Something like...
RuleFor(x => x.Value).IsEnumName(typeof(EnumType))
(x.Value should have whitespaces removed first)
The FluentValidation method Transform is designed for this case. The following uses the basic white space remover from Everson's answer:
RuleFor(x => x.Value)
.Transform(x => x.Replace(" ", string.Empty))
.IsEnumName(typeof(EnumType));
I'd opt to go with a stronger white space remover that catches tabs etc
RuleFor(x => x.Value)
.Transform(x => new string(x.Where(c => !Char.IsWhiteSpace(c)).ToArray()))
.IsEnumName(typeof(EnumType));
No need for a custom validator (e.g., Must) and personally I'd avoid writing one unless there was no other way.
I'd pop that white space remover into an extension (MVP, you should handle the null case; my preference would be a null guard but that's another topic):
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target){
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
Then the rule is a lot more readable:
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
Something to be aware of: I found that if Transform returned null the IsEnumName rule will pass. Personally I don't like that so I'd include a When rule builder option to only test when Value is provided, or a not empty rule to ensure it is provided.
Working LINQPad sample:
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Foo
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType));
.When(x => x != null);
}
}
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target)
{
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
void Main()
{
var validator = new FooValidator();
var foo1 = new Foo { Id = Guid.NewGuid(), Value = "Value 1" };
var result1 = validator.Validate(foo1);
Console.WriteLine(result1.IsValid);
var foo2 = new Foo { Id = Guid.NewGuid(), Value = "Value2" };
var result2 = validator.Validate(foo2);
Console.WriteLine(result2.IsValid);
var foo3 = new Foo { Id = Guid.NewGuid(), Value = "Value 3" };
var result3 = validator.Validate(foo3);
Console.WriteLine(result3.IsValid);
var foo4 = new Foo { Id = Guid.NewGuid(), Value = "This is not a valid enum value" };
var result4 = validator.Validate(foo4);
Console.WriteLine(result4.IsValid);
var foo5 = new Foo { Id = Guid.NewGuid(), Value = null };
var result5 = validator.Validate(foo5);
Console.WriteLine(result5.IsValid);
}
EDIT:
As per your additional comment about wrapping all of this into an extension:
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<T, string> IsEnumTypeMember<T>(this IRuleBuilderInitial<T, string> target)
{
return target
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
}
}
Then update the rule to use it:
RuleFor(x => x.Value).IsEnumTypeMember();
This is just an MVP, I don't really know all of your use cases; you may want to make it more generic so you could apply it to other enums.
You can use a custom method
RuleFor(x => x.Valor).Must(BeEnumType);
...
private bool BeEnumType(string v)
{
return Enum.IsDefined(typeof(EnumType), v.Replace(" ", string.Empty));
}
Related
I would like to have a generic way to map properties only if they are changed and not the default value.
So let's say you have these two classes
public class Source
{
public string Foo { get; set; } = "Foo";
public string Bar { get; set; } = "Bar";
}
public class Destination
{
public string Foo { get; set; } = "Foo of Destination";
public string Bar { get; set; } = "Bar of Destination";
}
Now I want to map Source to Destination and if the property of Source.Foo is still "Foo" it should change to "Foo of Destination", but when the value of Source.Foo is different from "Foo" (the default) it should really map the property.
I could not find a way to do this with automapper.
[TestMethod]
public void ChangeAllProperties()
{
var source = new Source();
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, defaultDestination.Bar); // Should be the Default Value of "Destination"
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void ChangeDefaultProperty()
{
var source = new Source();
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void AlLChanged()
{
var source = new Source();
source.Foo = "Custom Foo!";
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, source.Foo); // Should be Value of Source
}
Check out Automapper conditional mapping - link
Basically, you can do something like this (from the samples):
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
opt.Condition() makes sure your condition is met before actually doing any mapping of that particular member.
The opt.Condition() idea from Plamen Yordanov was the right path. I was able to create something more generic, so i dont need to define this for every member and checks the default value of the properties.
This is the solution i came up with. It only checks the default value if the destination member also exists in the source with the same name and type.
public class FooBarProfile: Profile
{
public FooBarProfile()
{
CreateMap<Source, Destination>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
// And if you need reversed...
CreateMap<Destination, Source>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
}
private static bool OnlyIfChanged<TSource>(TSource src, MemberInfo destinationMember) where TSource : new()
{
// Check if DestinationMember exists in TSource (same Name & Type)
var sourceMember = typeof(TSource).GetMembers().FirstOrDefault(e => e.Name == destinationMember.Name && e.MemberType == destinationMember.MemberType);
if (sourceMember != null)
{
var defaultOfSource = new TSource();
// Map only if Value of Source is differnent than the default soruce value
return (GetValue(sourceMember,src)?.Equals(GetValue(sourceMember,defaultOfSource)) == false);
}
return true;
}
// Helper-Method to get Value from a MemberInfo
private static object GetValue(MemberInfo memberInfo, object forObject)
{
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberInfo).GetValue(forObject);
case MemberTypes.Property:
return ((PropertyInfo)memberInfo).GetValue(forObject);
default:
throw new ArgumentException("Member is not Field or Property.", nameof(memberInfo));
}
}
}
I would like to know what's a better way to improve this Linq set of code.
Using xamarin forms and data binding. I need to pass individual fields to different labels and they all come from the same main list. I need to select those individuals items to pass them to each of the respective binding fields since each is expecting an only string.
var nameloc = MainList.Select(d => string.IsNullOrEmpty(d.name) ? "Not Available":d.name).FirstOrDefault();
var descrip = MainList.Select(d => string.IsNullOrEmpty(d.descr) ? "Not Available":d.descr).FirstOrDefault();
var cap = MainList.Select(d => string.IsNullOrEmpty(d.cap) ? "Not Available":d.cap).FirstOrDefault();
var ruls= MainList.Select(d => string.IsNullOrEmpty(d.ruls) ? "Not Available":d.ruls).FirstOrDefault();
var name = MainList.Select(d => string.IsNullOrEmpty(d.nameAddress) ? "Not Available":d.nameAddress).FirstOrDefault();
var email = MainList.Select(d => string.IsNullOrEmpty(d.EmailAddress) ? "Not Available":d.EmailAddress).FirstOrDefault();
var phone = MainList.Select(d => string.IsNullOrEmpty(d.phone) ? "Not Available":d.phone).FirstOrDefault();
LC = nameloc;
Description = descrip;
Cat = cap;
Rules = ruls;
Name = name;
Phone = phone;
email = email;
Note that your code has the perhaps slightly odd behaviour that if MainList is empty, each of the final strings are all null.
If this is intentional, you can add a helper function like this:
string ValueOrNotAvalable(MainListItem item, string value)
{
if (item == null)
return null;
return string.IsNullOrEmpty(value) ? "Not Available" : value;
}
var firstItem = MainList.FirstOrDefault();
LC = ValueOrNotAvalable(firstItem, firstItem?.name);
Description = ValueOrNotAvailable(firstItem, firstItem?.descr);
...
If you actually wanted to use the string "Not Available" if MainList is empty, you can simplify it a bit:
string ValueOrNotAvailable(string value) => string.IsNullOrEmpty(value) ? "Not Available" : value;
var firstItem = MainList.FirstOrDefault();
LC = ValueOrNotAvalable(firstItem?.name);
Description = ValueOrNotAvailable(firstItem?.descr);
...
I suggest you this approach:
var item = MainList.Select(x =>
new {
name = string.IsNullOrEmpty(x.name) ? "Not Available" : x.name,
//Same for other properties
}
).FirstOrDefault();
LC = item.name;
//Same for other properties
It makes no sense to use Linq in this way. The only collection-oriented operation in your code is the call to .FirstOrDefault(). Simply do that, and then add a helper method for getting the fallback string if the input is null or empty, e.g.:
public static string GetPropertyWithFallback(Item item, Func<Item, string> getter) {
if (item != null) {
var val = getter(item);
if (!string.IsNullOrEmpty(val)) {
return val;
}
}
return "Not available";
}
public static void Main() {
var mainList = new List<Item>();
var firstItem = mainList.FirstOrDefault();
var name = GetPropertyWithFallback(firstItem, i => i.Name);
var descr = GetPropertyWithFallback(firstItem, i => i.Descr);
//etc.
}
I think the minimalist approach would be using a lambda expression as parameter to get your values. So something like this might work -
//assumed class for demonstration only
public class Data
{
public string name { get; set; }
public string desc { get; set; }
}
//extension method
public static class ListExtensions
{
public static string GetFirstProperty(this List<Data> list, Expression<Func<Data, string>> predicate)
{
//you can ignore the compilation here, if you use Func<Data,string> instead of Expression<Func<Data,string>>
var compiledSelector = predicate.Compile();
return list.Select(x => string.IsNullOrWhiteSpace(compiledSelector(x)) ? "Not Available" : compiledSelector(x)).FirstOrDefault();
}
}
var name = MainList.GetFirstProperty(x => x.name);
var desc = MainList.GetFirstProperty(x => x.desc);
I assumed the data class. Use your own class as needed.
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm attempting to use a variable inside of a LINQ select statement.
Here is an example of what I'm doing now.
using System;
using System.Collections.Generic;
using System.Linq;
using Faker;
namespace ConsoleTesting
{
internal class Program
{
private static void Main(string[] args)
{
List<Person> listOfPersons = new List<Person>
{
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person()
};
var firstNames = Person.GetListOfAFirstNames(listOfPersons);
foreach (var item in listOfPersons)
{
Console.WriteLine(item);
}
Console.WriteLine();
Console.ReadKey();
}
public class Person
{
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person()
{
FirstName = NameFaker.Name();
LastName = NameFaker.LastName();
City = LocationFaker.City();
CountryName = LocationFaker.Country();
}
public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
}
}
}
I have a Some very not DRY code with the GetListOf... Methods
i feel like i should be able to do something like this
public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
{
return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
}
but that is not vaild code. I think the key Might Relate to Creating a Func
if That is the answer how do I do that?
Here is a second attempt using refelection But this is also a no go.
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Person person = new Person();
Type t = person.GetType();
PropertyInfo prop = t.GetProperty(property);
return listOfPersons.Select(prop).Distinct().OrderBy(x =>
x).ToList();
}
I think the refection might be a DeadEnd/red herring but i thought i would show my work anyway.
Note Sample Code is simplified in reality this is used to populate a datalist via AJAX to Create an autocomplete experience. That object has 20+ properties and I can complete by writing 20+ methods but I feel there should be a DRY way to complete this. Also making this one method also would clean up my controller action a bunch also.
Question:
Given the first section of code is there a way to abstract those similar methods into a single method buy passing some object into the select Statement???
Thank you for your time.
You would have to build the select
.Select(x =>x.property).
by hand. Fortunately, it isn't a tricky one since you expect it to always be the same type (string), so:
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);
Then the Select above becomes:
.Select(lambda).
(for LINQ based on IQueryable<T>) or
.Select(lambda.Compile()).
(for LINQ based on IEnumerable<T>).
Note that anything you can do to cache the final form by property would be good.
From your examples, I think what you want is this:
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (string)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
typeof is a built-in operator in C# that you can "pass" the name of a type to and it will return the corresponding instance of Type. It works at compile-time, not runtime, so it doesn't work like normal functions.
PropertyInfo has a GetValue method that takes an object parameter. The object is which instance of the type to get the property value from. If you are trying to target a static property, use null for that parameter.
GetValue returns an object, which you must cast to the actual type.
person => (string)prop.GetValue(person) is a lamba expression that has a signature like this:
string Foo(Person person) { ... }
If you want this to work with any type of property, make it generic instead of hardcoding string.
public static List<T> GetListOfProperty<T>(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (T)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
I would stay away from reflection and hard coded strings where possible...
How about defining an extension method that accepts a function selector of T, so that you can handle other types beside string properties
public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)
{
return instance
.Select(selector)
.Distinct()
.OrderBy(x => x)
.ToList();
}
and imagine that you have a person class that has an id property of type int besides those you already expose
public class Person
{
public int Id { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
all you need to do is fetch the results with type safe lambda selectors
var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);
Edit
As it seems you really need hardcoded strings as the property inputs, how about leaving out some dynamism and use a bit of determinism
public static List<string> Query(this IEnumerable<Person> instance, string property)
{
switch (property)
{
case "ids": return instance.Query(p => p.Id.ToString());
case "firstName": return instance.Query(p => p.FirstName);
case "lastName": return instance.Query(p => p.LastName);
case "countryName": return instance.Query(p => p.CountryName);
case "cityName": return instance.Query(p => p.City);
default: throw new Exception($"{property} is not supported");
}
}
and access the desired results as such
var cityNames = listOfPersons.Query("cityName");
You should be able to do it with Reflection. I use it something similar.
Just change your reflection try to this:
public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)
{
var ret = new List<string>();
PropertyInfo prop = typeof(Person).GetProperty(propertyName);
if (prop != null)
ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();
return ret;
}
I hope it helps.
It's based on C# 6
You can also use this. works for me.
public static class ObjectReflectionExtensions
{
public static object GetValueByName<T>(this T thisObject, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
return prop.GetValue(thisObject);
}
}
And call like this.
public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
{
return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
}
If you want to select all the values:
object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();
I have an XML file with a number of Units:
<Unit Name="Length">
<Prefix Char="c"
IsSelected="false"
Factor="1"/>
<Prefix Char="d"
IsSelected="true"
Factor="104"/>
</Unit>
I want to read an entire object:
public static Dictionary<string, Unit> Units { get; set; }
public class Prefix
{
public Func<double, double> fnc = null;
public Prefix(string c, double f, bool i, bool ifix = false,string fn = null)
{
Char = c;
Factor = f;
IsFixed = ifix;
Unit.funcs.TryGetValue(fn, out fnc);
}
public bool IsSelected { get; set; }
public bool IsFixed { get; set; }
public double Factor { get; set; }
public string Char { get; set; }
}
public Unit() { }
public Unit(string n, List<Prefix> p)
{
_name = n;
Prefixes = p;
}
private List<Prefix> _prefixes;
public List<Prefix> Prefixes
{
get { return _prefixes; }
set { _prefixes = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
....
}
I now have this:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
(
x.Attribute("Name").Value,
(List<Prefix>) x.Descendants("Prefix").Select(p => new Prefix(
p.Attribute("Char").Value,
Convert.ToDouble(p.Attribute("Factor").Value),
p.Attribute("IsSelected").Value == "true",
p.Attribute("IsFixed").Value == "true",
p.Attribute("Func").Value)
)
)
)
).ToDictionary(x => x.Name, x => x);
and get the following error:
"Unable to cast object of type
'WhereSelectEnumerableIterator2[System.Xml.Linq.XElement,DD.Prefix]'
to type 'System.Collections.Generic.List1[DD.Prefix]'."
Apparently there is something wrong with the (List<Prefix>)
What would the query have to be then? How do I get stuff in the List<>?
.
I would express this code in the following manner.. for it avoids doing parameter constructors that would constrain these types to IEnuermable expressions. i.e. avoid doing parameter constructors on types you plan to use for querying..
I'm placeholding the types that are using in/by Decendents as XmlElement type... but I'm sure that's inaccurate.. just replace it with whatever is the correct type.
Additionally, this snippet doesn't account for Unit.funcs.TryGetValue(fn, out fnc);.. and it presumes there is property name Func on the Prefix type. You can perform null checks during he assignment/setting..
data.Descendants("Unit")
.Select<XmlElement, Unit>(x => new Unit()
{
Name = x.Attribute("Name").Value,
Prefixes = x.Descendants("Prefix").Select<XmlElement, Prefix>(p => new Prefix()
{
Char = p.Attribute("Char").Value,
Factor = Convert.ToDouble(p.Attribute("Factor").Value),
IsSelectd = p.Attribute("IsSelected").Value == "true",
IsFixed = p.Attribute("IsFixed").Value == "true",
Func = p.Attribute("Func").Value
}).ToList()
})
.Select<Unit, KeyValuePair<string, Unit>>(unit => new KeyValuePair<string, Unit>()
{
Key = x.Name,
Value = x
})
.ToList()
.ForEach( kvp => {
Form.Units.Add(kvp.Key, kvp.Value);
});
It's not a list as is but a query, so you can't just cast it to a list but you can call ToList on it to have it enumerated and a list returned:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
(
x.Attribute("Name").Value,
//(List<Prefix>) no casting needed
x.Descendants("Prefix").Select(p => new Prefix(
p.Attribute("Char").Value,
Convert.ToDouble(p.Attribute("Factor").Value),
p.Attribute("IsSelected").Value == "true",
p.Attribute("IsFixed").Value == "true",
p.Attribute("Func").Value)
).ToList() // you had an IEnumerable<Prefix> now this is a List<Prefix>
)
)
).ToDictionary(x => x.Name, x => x);
To expand upon my comments, there are a couple of things here. First, you can use the .ToList() extension method on the .Select to convert the IEnumerable<T> collection to a List<T>.
Secondly, you will get a null reference exception if any attributes or elements are missing in the query. You can handle this safely by explicitly casting to string (and then converting the result if needed).
The updated query would look like this:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
((string)x.Attribute("Name"),
x.Descendants("Prefix").Select(p => new Prefix(
(string)p.Attribute("Char"),
Convert.ToDouble((string)p.Attribute("Factor")),
(string)p.Attribute("IsSelected") == "true",
(string)p.Attribute("IsFixed") == "true",
(string)p.Attribute("Func")).ToList()
)
)
)
).ToDictionary(x => x.Name, x => x);
Note that you don't need .Value when using (string) (since .Value is already string).
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();