Reputation: 16320
I have a MainEntity
class and it has a collection of SubEntity
. The following is the current validation:
public class MainEntityValidator : AbstractValidator<MainEntity>
{
public MainEntityValidator()
{
RuleFor(x => x.SubEntities).SetCollectionValidator(new SubEntityValidator());
}
public class SubEntityValidator : AbstractValidator<SubEntity>
{
public SubEntityValidator()
{
RuleFor(x => x.Field1).NotNull();
RuleFor(x => x.Field2).NotNull();
}
}
}
How can I add a validation rule so that only unique SubEntity
objects (based on Field1
and Field2
) must be in the collection?
Upvotes: 5
Views: 9278
Reputation: 6782
If you need to apply validation rule to collection property but still need access to main model and(or) whole collection, not only item being validated, then RuleForEach
method is your choice:
var comparer = new SubEntityComparer();
RuleForEach(x => x.SubEntities)
.Must((model, submodel) => model.SubEntities.Count(xsub => comparer.Equals(xsub, submodel)) == 1) // one match that ReferenceEquals hit
.WithMessage("The item with values {0}, {1} has duplicates in collection of {2} items",
(model, submodel) => submodel.Field1,
(model, submodel) => submodel.Field2,
(model, submodel) => model.SubEntities.Count); // in validation message generation you can access to current item as well as to main model
If you need only one error message for validation rule you described — you can apply simple predicate rule to collection property SubEntites
:
RuleFor(x => x.SubEntities)
.Must(coll => coll.Distinct(new SubEntityComparer()).Count() == coll.Count)
.WithMessage("One or more items in collection of {0} items are duplicates",
(model, coll) => coll.Count); // has access to collection and to main model
In both cases I used the same equality comparer, but you can override Equals
method as well, and use overloads of IEnumerable
extension methods with overload, that exclude EqualityComparer
parameter.
Code of EqualityComparer listed below:
public class SubEntityComparer : IEqualityComparer<SubEntity>
{
public bool Equals(SubEntity x, SubEntity y)
{
if (x == null ^ y == null)
return false;
if (ReferenceEquals(x, y))
return true;
// your equality comparison logic goes here:
return x.Field1 == y.Field1 &&
x.Field2 == y.Field2;
}
public int GetHashCode(SubEntity obj)
{
return obj.Field1.GetHashCode() + 37 * obj.Field2.GetHashCode();
}
}
Update:
In both ways to implement validation for collection you still can use SetCollectionValidator(new SubEntityValidator())
to validate each item with simple rules independently.
Upvotes: 8
Reputation: 121
I suppose the answer to your question is to compare every item in your collection to every other item and fail validation when to objects are "equal". Though I would not recommend doing that if your collection could contain more than a trivial number of items. A better approach would be to implement Equals() and GetHashCode() so that the collection types in the base class library will handle your SubEntity objects predictably. I would post an example but I don't know what your objects look like as you have not posted them. At any rate, if you implement those methods in a meaningful way you can define your collection as a type that already has the appropriate unique constraints (HashSet of T for instance).
Upvotes: 0