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));
}
I have a class with large amount of properties that I need to group by almost all columns.
class Sample {
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
public string S4 { get; set; }
// ... all the way to this:
public string S99 { get; set; }
public decimal? N1 { get; set; }
public decimal? N2 { get; set; }
public decimal? N3 { get; set; }
public decimal? N4 { get; set; }
// ... all the way to this:
public decimal? N99 { get; set; }
}
From time to time I need to group by all columns except one or two decimal columns and return some result based on this (namely object with all the fields, but with some decimal value as a sum or max).
Is there are any extension method that would allow me to do something like this:
sampleCollection.GroupByExcept(x => x.N2, x => x.N5).Select(....);
instead of specifying all columns in object?
You won't find anything builtin that handles such a case. You'd have to create one yourself. Depending on how robust you need this to be, you could take a number of approaches.
The main hurdle you'll come across is how you'll generate the key type. In an ideal situation, the new keys that are generated would have their own distinct type. But it would have to be dynamically generated.
Alternatively, you could use another type that could hold multiple distinct values and still could be suitably used as the key. Problem here is that it will still have to be dynamically generated, but you will be using existing types.
A different approach you could take that doesn't involve generating new types, would be to use the existing source type, but reset the excluded properties to their default values (or not set them at all). Then they would have no effect on the grouping. This assumes you can create instances of this type and modify its values.
public static class Extensions
{
public static IQueryable<IGrouping<TSource, TSource>> GroupByExcept<TSource, TXKey>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector) =>
GroupByExcept(source, exceptKeySelector, s => s);
public static IQueryable<IGrouping<TSource, TElement>> GroupByExcept<TSource, TXKey, TElement>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector, Expression<Func<TSource, TElement>> elementSelector)
{
return source.GroupBy(BuildKeySelector(), elementSelector);
Expression<Func<TSource, TSource>> BuildKeySelector()
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.MemberInit(
Expression.New(typeof(TSource).GetConstructor(Type.EmptyTypes)),
from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select Expression.Bind(p, Expression.Property(itemExpr, p))
);
return Expression.Lambda<Func<TSource, TSource>>(keyExpr, itemExpr);
}
}
}
Then to use it you would do this:
sampleCollection.GroupByExcept(x => new { x.N2, x.N5 })...
But alas, this approach won't work under normal circumstances. You won't be able to create new instances of the type within a query (unless you're using Linq to Objects).
If you're using Roslyn, you could generate that type as needed, then use that object as your key. Though that'll mean you'll need to generate the type asynchronously. So you probably will want to separate this from your query all together and just generate the key selector.
public static async Task<Expression<Func<TSource, object>>> BuildExceptKeySelectorAsync<TSource, TXKey>(Expression<Func<TSource, TXKey>> exceptKeySelector)
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var properties =
(from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select p).ToList();
var targetType = await CreateTypeWithPropertiesAsync(
properties.Select(p => (p.PropertyType, p.Name))
);
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.New(
targetType.GetConstructors().Single(),
properties.Select(p => Expression.Property(itemExpr, p)),
targetType.GetProperties()
);
return Expression.Lambda<Func<TSource, object>>(keyExpr, itemExpr);
async Task<Type> CreateTypeWithPropertiesAsync(IEnumerable<(Type type, string name)> properties) =>
(await CSharpScript.EvaluateAsync<object>(
AnonymousObjectCreationExpression(
SeparatedList(
properties.Select(p =>
AnonymousObjectMemberDeclarator(
NameEquals(p.name),
DefaultExpression(ParseTypeName(p.type.FullName))
)
)
)
).ToFullString()
)).GetType();
}
To use this:
sampleCollection.GroupBy(
await BuildExceptKeySelector((CollectionType x) => new { x.N2, x.N5 })
).Select(....);
Borrowing from this answer here:
Create a class EqualityComparer
public class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
IDictionary<string, object> xP = x as IDictionary<string, object>;
IDictionary<string, object> yP = y as IDictionary<string, object>;
if (xP.Count != yP.Count)
return false;
if (xP.Keys.Except(yP.Keys).Any())
return false;
if (yP.Keys.Except(xP.Keys).Any())
return false;
foreach (var pair in xP)
if (pair.Value.Equals( yP[pair.Key])==false)
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
Then create your GroupContent method:
private void GroupContent<T>(List<T> dataList, string[] columns, string[] columnsToExclude)
{
string[] columnsToGroup = columns.Except(columnsToExclude).ToArray();
EqualityComparer<IDictionary<string, object>> equalityComparer = new EqualityComparer<IDictionary<string, object>>();
var groupedList = dataList.GroupBy(x =>
{
var groupByColumns = new System.Dynamic.ExpandoObject();
((IDictionary<string, object>)groupByColumns).Clear();
foreach (string column in columnsToGroup)
((IDictionary<string, object>)groupByColumns).Add(column, GetPropertyValue(x, column));
return groupByColumns;
}, equalityComparer);
foreach (var item in groupedList)
{
Console.WriteLine("Group : " + string.Join(",", item.Key));
foreach (object obj in item)
Console.WriteLine("Item : " + obj);
Console.WriteLine();
}
}
private static object GetPropertyValue(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
I extended the code above borrowing another answer.
public static class IEnumerableExt {
public static IEnumerable<T> GroupBye<T, C>(this IEnumerable<T> query, Func<IGrouping<IDictionary<string, object>, T>, C> grouping) where T : class
{
var cProps = typeof(C).GetProperties().Select(prop => prop.Name).ToArray();
var columnsToGroup = typeof(T).GetProperties().Select(prop => prop.Name).Except(cProps).ToArray();
var equalityComparer = new EqualityComparer<IDictionary<string, object>>();
return query
.GroupBy(x => ExpandoGroupBy(x, columnsToGroup), equalityComparer)
.Select(x => MergeIntoNew(x, grouping, cProps));
}
private static IDictionary<string, object> ExpandoGroupBy<T>(T x, string[] columnsToGroup) where T : class
{
var groupByColumns = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
groupByColumns.Clear();
foreach (string column in columnsToGroup)
groupByColumns.Add(column, typeof(T).GetProperty(column).GetValue(x, null));
return groupByColumns;
}
private static T MergeIntoNew<T, C>(IGrouping<IDictionary<string, object>, T> x, Func<IGrouping<IDictionary<string, object>, T>, C> grouping, string[] cProps) where T : class
{
var tCtor = typeof(T).GetConstructors().Single();
var tCtorParams = tCtor.GetParameters().Select(param => param.Name).ToArray();
//Calling grouping lambda function
var grouped = grouping(x);
var paramsValues = tCtorParams.Select(p => cProps.Contains(p) ? typeof(C).GetProperty(p).GetValue(grouped, null) : x.Key[p]).ToArray();
return (T)tCtor.Invoke(paramsValues);
}
private class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
var xDict = x as IDictionary<string, object>;
var yDict = y as IDictionary<string, object>;
if (xDict.Count != yDict.Count)
return false;
if (xDict.Keys.Except(yDict.Keys).Any())
return false;
if (yDict.Keys.Except(xDict.Keys).Any())
return false;
foreach (var pair in xDict)
if (pair.Value == null && yDict[pair.Key] == null)
continue;
else if (pair.Value == null || !pair.Value.Equals(yDict[pair.Key]))
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
}
Which can be used in the following way:
var list = enumerable.GroupBye(grp => new
{
Value = grp.Sum(val => val.Value)
});
The result will like grouping all other columns but Value, which will be valued to the sum of grouped elements' value
I have two objects of type IList:
public class SampleSentence
{
public int SampleSentenceId { get; set; }
public string Text { get; set; }
}
IList<SampleSentence> Old =
[new SampleSentence() { SampleSentenceId = 1; Text = 'cat' }]
IList<SampleSentence> New =
[new SampleSentence() { Text = 'cat' }],
new SampleSentence() { Text = 'dog' }]
What I need to get is:
IList<SampleSentence> whatINeed =
[new SampleSentence() { Text = 'dog' }]
Object Old is a list of SampleSentences with the SampleSentenceId and Text fields populated.
Object New is a list of SampleSentences with the only the Text fields populated. It will have the same or more rows than object Old
Using LINQ how can I compare the Old and New objects (linking them with the contents of Text) and create another IList that has the additional columns in the list named New?
You can use simple antijoin implemented with LINQ GroupJoin method/operator:
IList<SampleSentence> whatINeed = New.GroupJoin(Old,
newElement => newElement.Text, oldElement => oldElement.Text,
(newElement, oldElements) => new { newElement, oldElements })
.Where(match => !match.oldElements.Any())
.Select(match => match.newElement)
.ToList();
The same with query syntax (preferable when using joins due to transparent identifiers - note the lack of match anonymous type):
IList<SampleSentence> whatINeed =
(from newElement in New
join oldElement in Old on newElement.Text equals oldElement.Text into oldElements
where !oldElements.Any()
select newElement)
.ToList();
Create a custom IEqualityComparer:
public class SampleSentenceComparer : IEqualityComparer<SampleSentence> {
public bool Equals(SampleSentence x, SampleSentence y) {
if (x == y) return true;
if (x == null || y == null) return false;
return x.Text.Equals(y.Text);
}
public int GetHashCode(SampleSentence obj) {
return obj.Text.GetHashCode();
}
}
Usage:
List<SampleSentence> newItems = New.Except(Old, new SampleSentenceComparer()).ToList();
How to convert this sample foreach into lambda expression?
foreach (ADOMD.Member iMember in pMemberCollection)
{
decimal lDimensionValue = 0;
if (Decimal.TryParse(iMember.Name, out lDimensionValue))
lDimensionValues.Add(lDimensionValue);
}
lDimensionValues.Sort();
ADOMD.Member is a interface looks like
[TypeLibType(xxx)]
[Guid("xxxxx")]
public interface Member
{
[DispId(0)]
string Caption { get; }
[DispId(1610743817)]
int ChildCount { get; }
string Name { get; }
[DispId(1610743812)]
Member Parent { get; }
[DispId(1610743819)]
bool ParentSameAsPrev { get; }
[DispId(1610743815)]
}
lDimensionValues =
pMemberCollection
.Cast<ADOMD.Member>()
.Select(iMember => {
decimal lDimensionValue = 0;
if (Decimal.TryParse(iMember.Name, out lDimensionValue))
return (decimal?)lDimensionValue;
else return null;
})
.Where(x => x != null)
.Select(x => x.Value)
.OrderBy(x => x)
.ToList();
Very ugly and verbose. If we had a TryParseDecimal method it would be cleaner.
This is not a perfect case for LINQ. Among other reasons due to the legacy collection that requires a Cast apparently.
Had to try to do this in as few lines as possible, interesting problem, i would not convert your method to LINQ though, if it already works (what works works)
lDimensionValues = pMemberCollection.Where(a => {
decimal lDimensionValued;
return decimal.TryParse(a.Name, out lDimensionValued);
}).Select(a=> decimal.Parse(a.Name)).Sort();
Says I have ListA={null,3,2,null}.
ListA.OrderBy(x=>x.ID) //would return me null,null,2,3
If my objective is to get 2,3,null,null, currently I can only think of extracting out the null item, and manually pump into the back.
Is there a clean approach where it will return me 2,3,null,null?
You can use OrderByDescending + ThenBy(assuming that it's aList<int?>):
var orderedList = ListA
.OrderByDescending(x => x.HasValue)
.ThenBy(x => x);
x.HasValue returns true or false where true is higher than false. That's why i'm using OrderByDescending.
If you want to sort the original list i would use List.Sort with a custom Compaison<T> that treats null as highest value:
ListA.Sort((a1, a2) => (a1 ?? int.MaxValue).CompareTo(a2 ?? int.MaxValue));
This is more efficient since it doesn't need to create a new list.
As an alternative to Tim's answer you could write your own IComparer<T> which does the custom sorting algorithm for you.
var array = list.OrderBy(x => x, new NullableIntComparer())
.ToArray();
class NullableIntComparer : IComparer<int?>
{
public int Compare(int? x, int? y)
{
if (x.HasValue && y.HasValue)
{
return x.Value.CompareTo(y.Value);
}
if (x.HasValue)
{
return -1;
}
if (y.HasValue)
{
return 1;
}
return 0;
}
}
Tried the following:
class Program
{
class A
{
public A(){}
public int? ID { get; set; }
}
static void Main(string[] args)
{
var listA = new List<A>
{
new A(){ID = null},
new A(){ID = 2},
new A(){ID = null},
new A(){ID = 3},
};
var result = listA.OrderByDescending(x => x.ID != null).ThenBy(x => x.ID);
foreach (var a in result)
{
Console.WriteLine(a.ID);
}
}
}