Características Avanzadas
Esta página cubre las capacidades avanzadas de BaseExpression<TBuilder, T>: construcción y compilación de expresiones, helpers de validación, adjuntar mensajes, operadores lógicos, condiciones condicionales, agrupación de sub-grupos y seguridad en concurrencia.
Ejemplos: ValiFlow · Evaluators
Construir Expresiones
Build()
Devuelve el árbol de expresiones Expression<Func<T, bool>> compuesto. El árbol se recompila en cada llamada, a menos que se use la versión en caché.
Expression<Func<Order, bool>> expr = rule.Build();
var results = await dbContext.Orders.Where(expr).ToListAsync();
BuildCached()
Devuelve un delegado Func<T, bool> compilado. El delegado compilado se almacena en caché internamente tras la primera llamada — las llamadas posteriores devuelven la misma instancia.
Func<Order, bool> predicate = rule.BuildCached();
var valid = orders.Where(predicate).ToList();
BuildNegated()
Devuelve un árbol de expresiones negado — el complemento lógico de Build().
Expression<Func<Order, bool>> notExpr = rule.BuildNegated();
var invalid = await dbContext.Orders.Where(notExpr).ToListAsync();
Helpers de Validación
IsValid
Devuelve true cuando la entidad satisface todas las condiciones.
bool ok = rule.IsValid(entity);
IsNotValid
Devuelve true cuando la entidad falla al menos una condición.
bool failed = rule.IsNotValid(entity);
Validate
Devuelve un ValidationResult para la entidad con información de error estructurada.
ValidationResult result = rule.Validate(entity);
Console.WriteLine(result.IsValid); // bool
Console.WriteLine(result.ErrorMessage); // string — primer mensaje de fallo
Console.WriteLine(result.PropertyPath); // string — adjuntado con WithError()
Console.WriteLine(result.Severity); // enum Severity
Short-circuit por grupos OR:
Validate()evalúa los grupos OR de uno en uno. En cuanto un grupo pasa (todas sus condiciones AND se cumplen),Validate()retornaOk()inmediatamente sin evaluar los grupos restantes. Solo cuando todos los grupos fallan se recopilan y retornan los errores.
ValidateAll
Devuelve un IEnumerable<ValidationResult> para una colección de entidades.
IEnumerable<ValidationResult> all = rule.ValidateAll(entities);
var errors = all.Where(r => !r.IsValid).ToList();
Explain
Devuelve un string legible que describe qué condiciones pasaron o fallaron para una entidad dada. Útil para depuración y logging.
string explanation = rule.Explain(entity);
Console.WriteLine(explanation);
WithMessage / WithError
Adjunta un mensaje descriptivo o una ruta de propiedad a la condición añadida más recientemente.
WithMessage(string)
Adjunta un mensaje de error estático.
rule.MinLength(x => x.Name, 3)
.WithMessage("El nombre debe tener al menos 3 caracteres.");
WithMessage(Func<string>)
Adjunta un mensaje evaluado de forma diferida o localizado.
rule.MinLength(x => x.Name, 3)
.WithMessage(() => Resources.NameTooShort);
Nota: La función factory no debe retornar
null. Si retornanull, la condición se trata como si no tuviera mensaje y será excluida silenciosamente delValidationResultaunque la condición haya fallado.
WithError(string)
Adjunta una ruta de propiedad para identificar qué campo falló.
rule.MinLength(x => x.Name, 3)
.WithMessage("El nombre es demasiado corto")
.WithError("Name");
Combinando los tres:
rule.NotNull(x => x.CustomerId)
.WithMessage(() => Resources.CustomerRequired)
.WithError(nameof(Order.CustomerId));
Operadores Lógicos (And / Or)
And (predeterminado)
Las condiciones se combinan con AND por defecto. No es necesario llamar a And() explícitamente.
var rule = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active") // AND
.GreaterThan(x => x.Total, 0m); // AND
Or()
Inserta un OR lógico entre la condición anterior y la siguiente.
var rule = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active")
.Or()
.EqualTo(x => x.Status, "Pending");
// Produce: Status == "Active" OR Status == "Pending"
Mezcla de And y Or:
var rule = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active")
.GreaterThan(x => x.Total, 0m) // AND
.Or()
.EqualTo(x => x.Status, "Pending") // OR
.GreaterThan(x => x.Total, 0m); // AND (aplica a la rama Pending)
Nota: Las condiciones conectadas con
Andse evalúan antes que los grupos separados porOr. El builder agrupa las condicionesAndconsecutivas antes de combinar grupos conOr— consistente con la precedencia estándar de operadores booleanos.
When / Unless (Condiciones Condicionales)
Aplica condiciones solo cuando un indicador en tiempo de ejecución es verdadero o falso.
When
Añade las condiciones internas solo cuando el predicado es true.
rule.When(order.IsInternational, r =>
r.IsNotNullOrEmpty(x => x.CustomsCode));
Unless
Añade las condiciones internas solo cuando el predicado es false.
rule.Unless(order.IsDraft, r =>
r.NotNull(x => x.CustomerId));
Combinando ambos:
var rule = new ValiFlow<Order>()
.NotNull(x => x.Total)
.When(order.IsInternational, r =>
r.IsNotNullOrEmpty(x => x.CustomsCode)
.IsNotNullOrEmpty(x => x.DestinationCountry))
.Unless(order.IsDraft, r =>
r.NotNull(x => x.CustomerId)
.GreaterThan(x => x.Total, 0m));
AddSubGroup — Lógica Agrupada
Usa AddSubGroup para crear sub-expresiones agrupadas explícitamente. Es el equivalente explícito de encadenar .Or() entre condiciones, pero envuelve una sub-regla completa como una única unidad agrupada.
// (Type == "A" AND Amount > 100) OR (Type == "B" AND Amount < 50)
var rule = new ValiFlow<Order>()
.AddSubGroup(g => g
.EqualTo(x => x.Type, "A")
.GreaterThan(x => x.Amount, 100))
.Or()
.AddSubGroup(g => g
.EqualTo(x => x.Type, "B")
.LessThan(x => x.Amount, 50));
Nota de diseño:
AddSubGroup(g => ...)yAdd().Or().Add()producen árboles de expresiones equivalentes. Mantenlos consistentes al escribir reglas compuestas.
Freeze / Clone — Seguridad en Concurrencia
Freeze()
Sella el builder para que no se puedan añadir más condiciones. Cualquier llamada a Add() después de Freeze() devuelve un fork (una nueva copia independiente) en lugar de mutar el original. Útil para compartir una regla base entre hilos.
var baseRule = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active");
baseRule.Freeze(); // sellarlo
// Las adiciones posteriores devuelven un fork, no una mutación
var extendedRule = baseRule.GreaterThan(x => x.Total, 1000m);
// baseRule no cambia; extendedRule es una nueva instancia
Clone()
Crea una copia superficial O(1) del builder. El clon se recompila en su primer uso. Útil para crear variantes por solicitud o por hilo.
var baseRule = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active");
var premiumRule = baseRule.Clone();
premiumRule.GreaterThan(x => x.Total, 1000m); // solo afecta a premiumRule
bool isRegular = baseRule.IsValid(order);
bool isPremium = premiumRule.IsValid(order);
Severity
Las condiciones usan Severity.Error por defecto. Puedes adjuntar Severity.Warning a una condición para indicar un fallo no bloqueante.
Severity.Info — Informativo. Una condición con esta severidad solo aparece en ValidationResult cuando falla y tiene un mensaje adjunto (via WithMessage o WithError). Una condición Info que falla sin mensaje es excluida silenciosamente de todas las colecciones del resultado. Esto es intencional: Info representa una sugerencia, no un fallo bloqueante.
var rule = new ValiFlow<Order>()
.NotNull(x => x.Name)
.WithMessage("El nombre es obligatorio")
// Severity.Error es el valor predeterminado
.MinLength(x => x.Name, 2)
.WithMessage("El nombre es muy corto — considera un nombre más largo")
.WithSeverity(Severity.Warning);
ValidationResult result = rule.Validate(order);
if (!result.IsValid && result.Severity == Severity.Error)
{
// fallo crítico
}
else if (!result.IsValid && result.Severity == Severity.Warning)
{
// fallo leve — advertir al usuario
}
Mini ejemplos
1) Reusar predicado cacheado
var regla = new ValiFlow<Product>()
.GreaterThan(p => p.Stock, 0)
.IsTrue(p => p.IsActive);
Func<Product, bool> predicate = regla.BuildCached();
var available = products.Where(predicate).ToList();
2) Filtro negado para “fallidos”
var regla = new ValiFlow<Order>()
.IsTrue(o => o.IsPaid)
.GreaterThan(o => o.Total, 0m);
Expression<Func<Order, bool>> failed = regla.BuildNegated();
var notPaid = await dbContext.Orders.Where(failed).ToListAsync();