I have an entity that has a nested collection as a property which I will receive from a front-end application.
I just want to make sure that each item in this collection(ICollection ChildClassCollection) has a unique Id within the model I have received.
I am using FluentValidation and would like to add this validation using it too for consistency.
It's something very simple I couldn`t find an elegant way to solve..
An example:
public class ParentClass
{
public string Name { get; set; }
public ICollection<ChildClass> ChildClassCollection { get; set; }
}
public class ChildClass
{
public int Id { get; set; }
public string Name { get; set; }
}
Use HashSet. https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?view=netframework-4.7.2
public class ParentClass
{
public string Name { get; set; }
public HashSet<ChildClass> ChildClassCollection { get; set; }
}
public class ChildClass
{
public int Id { get; set; }
public string Name { get; set; }
}
Do you want to use only fluentValidator to achieve this without any relationship ?
If so, you can custom a validator,then use some logic to confirm whether there is a duplicate value
in database,like this:
public class ChildClassValidator : AbstractValidator<ChildClass>
{
private readonly MyDbContext _context;
public ChildClassValidator(MyDbContext context)
{
_context = context;
RuleFor(x => x.Id).NotEmpty().WithMessage("ID is required.").Must(IsUnique).WithMessage("parent have more than one ids");
}
private bool IsUnique(int id)
{
var model = _context.ParentClasses.GroupBy(x => x.Id)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.ToList(); //judge whether parentclass has duplicate id
if (model==null)
return true;
else return false;
}
}
Here is what I ended up with: PRetty cleand, plus, when it encounters issue, it will exit
this.RuleFor(or => or.ChildClassCollection)
.Must(this.IsDistinct)
.WithMessage("There are more than one entity with the same Id");
public bool IsDistinct(List<UpdateRoleDTO> elements)
{
var encounteredIds = new HashSet<int>();
foreach (var element in elements)
{
if (!encounteredIds.Contains(element.Id))
{
encounteredIds.Add(element.Id);
}
else
{
return false;
}
}
return true;
}
I use mine custom helper, based by ansver: Check a property is unique in list in FluentValidation . Because the helper is much more convenient to use than each time to describe the rules. Such a helper can be easily chained with other validators like IsEmpty
.
Code:
public static class FluentValidationHelper
{
public static IRuleBuilder<T, IEnumerable<TSource>> Unique<T, TSource, TResult>(
this IRuleBuilder<T, IEnumerable<TSource>> ruleBuilder,
Func<TSource, TResult> selector, string? message = null)
{
if (selector == null)
throw new ArgumentNullException(nameof(selector), "Cannot pass a null selector.");
ruleBuilder
.Must(x =>
{
var array = x.Select(selector).ToArray();
return array.Count() == array.Distinct().Count();
})
.WithMessage(message ?? "Elements are not unique.");
return ruleBuilder;
}
}
Usage:
For example we want to choose unique id
RuleFor(_ => _.ChildClassCollection!)
.Unique(_ => _.Id);
Or want choose unique id and name
RuleFor(_ => _.ChildClassCollection!)
.Unique(_ => new { _.Id, _.Name });
I optimization performance using code from answer YoannaKostova
public static class FluentValidationHelper
{
public static bool IsDistinct<TSource, TResult>(this IEnumerable<TSource> elements, Func<TSource, TResult> selector)
{
var hashSet = new HashSet<TResult>();
foreach (var element in elements.Select(selector))
{
if (!hashSet.Contains(element))
hashSet.Add(element);
else
return false;
}
return true;
}
public static IRuleBuilder<T, IEnumerable<TSource>> Unique<T, TSource, TResult>(
this IRuleBuilder<T, IEnumerable<TSource>> ruleBuilder,
Func<TSource, TResult> selector, string? message = null)
{
if (selector == null)
throw new ArgumentNullException(nameof(selector), "Cannot pass a null selector.");
ruleBuilder
.Must(x => x.IsDistinct(selector))
.WithMessage(message ?? "Elements are not unique.");
return ruleBuilder;
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.