Skip to content

Instantly share code, notes, and snippets.

@Lippur
Last active July 17, 2021 20:21
Show Gist options
  • Save Lippur/37a4704086c6ccedbc144934931646fb to your computer and use it in GitHub Desktop.
Save Lippur/37a4704086c6ccedbc144934931646fb to your computer and use it in GitHub Desktop.
UniqueValidator - Checks if a value is unique in the database for the given entity property
using System;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Validators;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace Reflex.Core.Database.Rules
{
public static class UniqueRule
{
/// <summary>
/// Check if the given value is unique in the database for the given entity property
/// </summary>
/// <param name="ruleBuilder"></param>
/// <param name="entityExpression">Member access expression for the entity property (e.g. (User user) => user.Email)</param>
/// <param name="valueTransformation">Optional delegate applied to the value under validation before comparison</param>
/// <typeparam name="T">Type of the object under validation</typeparam>
/// <typeparam name="TProperty">Type of the property to be validated</typeparam>
/// <typeparam name="TEntity">Type of the entity to check against</typeparam>
/// <returns></returns>
public static IRuleBuilderOptions<T, TProperty> Unique<T, TProperty, TEntity> (
this IRuleBuilder<T, TProperty> ruleBuilder,
Expression<Func<TEntity, TProperty>> entityExpression,
Func<TProperty, TProperty>? valueTransformation = null
)
where TEntity : class
{
return ruleBuilder.SetAsyncValidator(
new UniqueValidator<T, TProperty, TEntity>(entityExpression, valueTransformation)
)
.WithMessage("Must be unique");
}
}
public class UniqueValidator<T, TProperty, TEntity> : AsyncPropertyValidator<T, TProperty>
where TEntity : class
{
private readonly Expression<Func<TEntity, TProperty>> _property;
private readonly Func<TProperty, TProperty>? _valueTransformation;
public UniqueValidator (
Expression<Func<TEntity, TProperty>> property,
Func<TProperty, TProperty>? valueTransformation = null
)
{
_property = property;
_valueTransformation = valueTransformation;
}
public override async Task<bool> IsValidAsync (
ValidationContext<T> context,
TProperty value,
CancellationToken cancellation
)
{
var db = context.GetServiceProvider()
.CreateScope()
.ServiceProvider.GetRequiredService<AppDb>()
.Set<TEntity>();
var transformedValue = _valueTransformation is null ? value : _valueTransformation(value);
var valueExpression = Expression.Constant(transformedValue);
var parameter = Expression.Parameter(typeof(TEntity));
var propertyExpression = Expression.Property(parameter, _property.GetPropertyAccess());
var equalityExpression =
Expression.Lambda<Func<TEntity, bool>>(
Expression.Equal(propertyExpression, valueExpression),
parameter
);
return !await db.AnyAsync(equalityExpression, cancellation);
}
public override string Name => "UniqueValidator";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment