Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FluentValidation checking for duplicate entity in a sub-collection

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?

like image 580
Ivan-Mark Debono Avatar asked May 19 '15 18:05

Ivan-Mark Debono


1 Answers

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.

like image 93
Evgeny Levin Avatar answered Oct 07 '22 16:10

Evgeny Levin