glazjoon
glazjoon

Reputation: 654

Fluent validator to check if entity with ID exists in database

I'm trying to write a custom validator that will check if an entity exists in the database, using OrmLite. The problem is that the type arguments for IRuleBuilder can no longer be inferred from usage.

I have to write the method call like this:

RuleFor(r => r.Id).Exists<DtoName, int, EntityName>()

But I want to write it like this:

Rulefor(r => r.Id).Exists<EntityName>()

This happens because IRuleBuilder has two type parameters and the method is an extension method. Is there a smart, fluent way to design this and make the function call preferably like the second version?

Here is code for my extension method and my validator:

    public static class AbstractValidatorExtensions
    {
        public static IRuleBuilderOptions<T, TProperty> Exists<T, TProperty, U>(this IRuleBuilder<T, TProperty> ruleBuilder)
        {
            return ruleBuilder.SetValidator(new EntityExistsValidator<U>());
        }                
    }

    public class EntityExistsValidator<T> : PropertyValidator
    {
        public EntityExistsValidator() : base("Entity does not exist") {}

        protected override bool IsValid(PropertyValidatorContext context)
        {
            return HostContext.Resolve<Repository>()
                .Exists<T>((int)context.PropertyValue);
        }
    }

Upvotes: 4

Views: 15785

Answers (3)

Shervin Ivari
Shervin Ivari

Reputation: 2601

As mentioned, in most cases and applications, database logic should be implemented somewhere else, like services or commands. However, I like the idea of checking some database logic which is related to validations in validators. You can use dependency injection as well in your AbstractValidator. You can achieve this by using Constructor and Rule or RuleAsync.

public class LoginCommandValidator : AbstractValidator<LoginCommand>
{
    public LoginCommandValidator(IDbContext context)
    {
        RuleFor(x => x.Username)
       .Must(c => context.Users.Any(u => u.UserName == c)).WithMessage("Already Exists");

        RuleFor(x => x.Username)
       .MustAsync(async (username, cancellation) =>
         await context.Users.AnyAsync(u => u.UserName == username, cancellation))
       .WithMessage("Already Exists");
    }
}

Upvotes: 2

thomasgalliker
thomasgalliker

Reputation: 1887

My experience with FluentValidation is that you’re trying to push more and more logic into validators. I would not do this as it adds too much complexity. My rule of thumb is to validate discrete property values only. Example: I would just use FluentValidation to check if property int Id is 0 or greater than 0. The check if the entity already exists I would move to another service (often called “the business logic”).

Upvotes: 17

mythz
mythz

Reputation: 143369

You'll need to a Custom Validator for custom validation to access dependencies, something like:

RuleFor(x => x.Id)
    .Must(id =>
    {
        using (var db = HostContext.AppHost.GetDbConnection(base.Request))
        {
            return !db.Exists<EntityName>(x => x.Id == id);
        }
    })
    .WithErrorCode("AlreadyExists")
    .WithMessage("...");

I'd also consider just doing validation that use dependencies in your Services instead:

if (Db.Exists<EntityName>(x => x.Id == request.Id))
    throw new ArgumentException("Already Exists", nameof(request.Id));

Upvotes: 4

Related Questions