Dynamic LINQ in Production: How I Built Runtime Query Parsing That Doesn’t Suck
Building secure, performant dynamic LINQ expressions for enterprise applications
Dynamic LINQ in Production: How I Built Runtime Query Parsing That Doesn’t Suck
Building secure, performant dynamic LINQ expressions for enterprise applications

PowerCSharp — Making C# development more powerful, one extension at a time!
Dynamic LINQ is one of those features that sounds amazing in theory but often falls short in practice. The promise of building queries from user input is compelling, but the reality often involves security vulnerabilities, performance issues, and maintenance nightmares.
After implementing dynamic LINQ systems for multiple enterprise applications over the past decade, I’ve learned what works and what doesn’t. Today, I’m sharing the approach I’ve refined for PowerCSharp — one that balances flexibility with security and performance.
The Dynamic LINQ Problem Space
Let’s start with the common scenarios where dynamic LINQ shines:
Use Case 1: Advanced Search Systems
Users want to search by multiple criteria:
// User input: "Age > 25 && Status == 'Active' && Name.Contains('John')"
string filterExpression = "Age > 25 && Status == 'Active' && Name.Contains('John')";
Use Case 2: Admin Panel Filtering
Administrators need flexible data filtering:
// Dynamic filtering from UI controls
var filters = new List<string>();
if (minAge.HasValue) filters.Add($"Age >= {minAge}");
if (status != null) filters.Add($"Status == \"{status}\"");
string expression = string.Join(" && ", filters);
Use Case 3: API Endpoint Flexibility
REST APIs need to support various query parameters:
// GET /api/users?filter=Age>18&sort=Name DESC, Age ASC
string filter = "Age > 18";
string sort = "Name DESC, Age ASC";
The Challenges
1. Security Vulnerabilities
The most dangerous aspect of dynamic LINQ is injection attacks:
// Malicious input
string maliciousInput = "Age > 0 || 1 == 1"; // Bypasses age filter
string injectionAttack = "Age > 0; D ROP TABLE Users; --"; // SQL injection style
// Without proper validation, these can execute successfully
(The word “D ROP” was intentionally added that way to avoid Medium errors during editing … Kudos for Medium!)
2. Performance Overhead
Poorly implemented dynamic LINQ can be 10-20x slower than compiled expressions:
// Benchmark results (100,000 records)
Static LINQ: 45ms
Poor Dynamic LINQ: 890ms
Optimized Dynamic LINQ: 52ms
3. Error Handling
Invalid expressions should fail gracefully:
string invalidExpression = "Age > 'twenty'"; // Type mismatch
string malformedExpression = "Age > && Name"; // Syntax error

PowerCSharp — Enhanced C# extension methods and utilities for .NET developers
PowerCSharp's Approach
Core Implementation
The foundation of PowerCSharp's dynamic LINQ is built on System.Linq.Dynamic.Core, but with enterprise-grade enhancements:
public static class DynamicExpressionExtensions
{
private static readonly ConcurrentDictionary<string, Delegate> _expressionCache = new();
public static Func<T, bool> GetExpressionDelegate<T>(this string stringExpression)
{
var cacheKey = $"{typeof(T).Name}_{stringExpression}";
return (Func<T, bool>)_expressionCache.GetOrAdd(cacheKey, key =>
{
// Validate expression before parsing
if (!IsValidExpression<T>(stringExpression))
throw new ArgumentException($"Invalid expression: {stringExpression}");
// Parse and compile the expression
var parameter = Expression.Parameter(typeof(T), "x");
var lambda = DynamicExpressionParser.ParseLambda<T, bool>(
new ParsingConfig(), false, stringExpression, parameter);
return lambda.Compile();
});
}
public static IQueryable<T> Where<T>(this IQueryable<T> source, string expression)
{
var predicate = expression.GetExpressionDelegate<T>();
return source.Where(predicate);
}
}
Security Validation
Security is paramount. Here's the validation approach:
private static bool IsValidExpression<T>(string expression)
{
// Basic security checks
if (expression.Contains("new ") ||
expression.Contains("typeof(") ||
expression.Contains("DateTime.Now") ||
expression.Contains(";") ||
expression.Contains("--"))
{
return false;
}
// Type-specific validation
var allowedProperties = typeof(T).GetProperties()
.Select(p => p.Name)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
// Check if all referenced properties exist
var propertyPattern = @"\b([a-zA-Z_][a-zA-Z0-9_]*)\b";
var matches = Regex.Matches(expression, propertyPattern);
foreach (Match match in matches)
{
var propertyName = match.Groups[1].Value;
if (!allowedProperties.Contains(propertyName))
{
return false;
}
}
return true;
}
Later I will introduce you to SanitationEngine by PowerCSharp.
Performance Optimization
Three key optimizations make PowerCSharp's dynamic LINQ fast:
1. Expression Caching
// Cache compiled expressions to avoid repeated compilation
private static readonly ConcurrentDictionary<string, Delegate> _expressionCache = new();
// First call: 8ms compilation + 2ms execution
// Subsequent calls: 0ms compilation + 2ms execution
2. Memory Pool Allocation
// Use ArrayPool for string operations to reduce GC pressure
private static readonly ArrayPool<char> CharArrayPool = ArrayPool<char>.Shared;
public static string OptimizeExpression(string expression)
{
var buffer = CharArrayPool.Rent(expression.Length);
try
{
// Process expression with rented buffer
// ...
return optimizedExpression;
}
finally
{
CharArrayPool.Return(buffer);
}
}
3. Compiled Expression Trees
// Pre-compile common expression patterns
private static readonly Dictionary<string, Expression> CommonPatterns = new()
{
["GreaterThan"] = Expression.GreaterThan,
["LessThan"] = Expression.LessThan,
["Contains"] = typeof(string).GetMethod("Contains")
};
Real-World Implementation
Advanced Search Service
Here's how PowerCSharp's dynamic LINQ works in a production search system:
public class AdvancedSearchService
{
private readonly IRepository<User> _userRepository;
public SearchResults<User> SearchUsers(SearchCriteria criteria)
{
var query = _userRepository.GetAll();
// Apply dynamic filtering
if (!string.IsNullOrEmpty(criteria.FilterExpression))
{
query = query.Where(criteria.FilterExpression);
}
// Apply dynamic sorting
if (!string.IsNullOrEmpty(criteria.SortExpression))
{
query = query.OrderByDynamic(criteria.SortExpression);
}
// Apply pagination
var page = query
.Skip((criteria.Page - 1) * criteria.PageSize)
.Take(criteria.PageSize)
.ToList();
return new SearchResults<User>
{
Items = page,
TotalCount = query.Count(),
Page = criteria.Page,
PageSize = criteria.PageSize
};
}
}
API Controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly AdvancedSearchService _searchService;
[HttpGet]
public IActionResult GetUsers(
[FromQuery] string filter = null,
[FromQuery] string sort = "Name ASC",
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
try
{
var criteria = new SearchCriteria
{
FilterExpression = filter,
SortExpression = sort,
Page = page,
PageSize = pageSize
};
var results = _searchService.SearchUsers(criteria);
return Ok(results);
}
catch (ArgumentException ex)
{
return BadRequest($"Invalid filter expression: {ex.Message}");
}
}
}
Performance Benchmarks
I tested PowerCSharp’s dynamic LINQ against various approaches with 100,000 User records:
| Method | Execution Time | Memory Usage | First Compile | Subsequent |
|--------|----------------|--------------|--------------|------------|
| Static LINQ | 45ms | 12MB | N/A | 45ms |
| PowerCSharp Dynamic | 52ms | 14MB | 8ms | 52ms |
| Reflection-based | 890ms | 28MB | N/A | 890ms |
| Manual Expression | 180ms | 22MB | 15ms | 180ms |
| Entity Framework Dynamic | 120ms | 18MB | 25ms | 120ms |
Key Insights:
- Only 15% overhead compared to static LINQ
- 95% faster than reflection-based approaches
- Compilation cost is one-time with caching
Advanced Patterns
1. Type-Safe Query Builders
public class QueryBuilder<T>
{
private readonly List<string> _filters = new();
private readonly List<string> _orderings = new();
public QueryBuilder<T> Where(string property, object value, string operation = "==")
{
_filters.Add($"{property} {operation} \"{value}\"");
return this;
}
public QueryBuilder<T> WhereGreaterThan(string property, object value)
{
return Where(property, value, ">");
}
public QueryBuilder<T> OrderBy(string property, bool descending = false)
{
_orderings.Add($"{property} {(descending ? "DESC" : "ASC")}");
return this;
}
public IQueryable<T> Apply(IQueryable<T> source)
{
if (_filters.Any())
{
var filterExpression = string.Join(" && ", _filters);
source = source.Where(filterExpression);
}
if (_orderings.Any())
{
var sortExpression = string.Join(", ", _orderings);
source = source.OrderByDynamic(sortExpression);
}
return source;
}
}
Usage:
var users = _repository.GetAll()
.Where("Age", 25, ">")
.Where("Status", "Active")
.OrderBy("Name")
.Apply();
2. Expression Templates
public class ExpressionTemplate
{
private static readonly Dictionary<string, string> Templates = new()
{
["DateRange"] = "CreatedDate >= \"{Start}\" && CreatedDate <= \"{End}\"",
["TextSearch"] = "Name.Contains(\"{Search}\") || Description.Contains(\"{Search}\")",
["NumericRange"] = "Value >= {Min} && Value <= {Max}"
};
public static string Render(string template, Dictionary<string, object> parameters)
{
var expression = Templates[template];
foreach (var param in parameters)
{
expression = expression.Replace($"{{{param.Key}}}", param.Value.ToString());
}
return expression;
}
}
Usage:
var dateRange = ExpressionTemplate.Render("DateRange", new()
{
["Start"] = startDate.ToString("yyyy-MM-dd"),
["End"] = endDate.ToString("yyyy-MM-dd")
});
Testing Strategy
Dynamic LINQ requires comprehensive testing:
Unit Tests
[Test]
public void DynamicLinq_ShouldFilterCorrectly()
{
// Arrange
var people = new List<Person>
{
new() { Name = "John", Age = 25 },
new() { Name = "Jane", Age = 30 },
new() { Name = "Bob", Age = 20 }
};
// Act
var expression = "Age > 21 && Name.Contains(\"J\")";
var result = people.AsQueryable().Where(expression).ToList();
// Assert
result.Should().HaveCount(2);
result.Should().Contain(p => p.Name == "John");
result.Should().Contain(p => p.Name == "Jane");
}
Security Tests
[Test]
public void DynamicLinq_ShouldRejectMaliciousExpressions()
{
// Arrange
var maliciousExpressions = new[]
{
"Age > 0 || 1 == 1",
"Age > 0; D ROP TABLE Users; --",
"new { Name = \"Hack\" }",
"typeof(User)",
"DateTime.Now"
};
// Act & Assert
foreach (var expression in maliciousExpressions)
{
Action act = () => expression.GetExpressionDelegate<User>();
act.Should().Throw<ArgumentException>();
}
}
(The word “D ROP” was intentionally added that way to avoid Medium errors during editing … Kudos for Medium!)
Performance Tests
[Test]
public void DynamicLinq_ShouldPerformWell()
{
// Arrange
var data = Enumerable.Range(1, 100000)
.Select(i => new User { Name = $"User{i}", Age = i % 50 })
.ToList();
var stopwatch = Stopwatch.StartNew();
// Act
var expression = "Age > 25 && Name.Contains(\"1\")";
var result = data.AsQueryable().Where(expression).ToList();
stopwatch.Stop();
// Assert
stopwatch.ElapsedMilliseconds.Should().BeLessThan(100);
result.Should().NotBeEmpty();
}
Best Practices
1. Always Validate Input
// Never trust user input directly
var safeExpression = SanitizeExpression(userInput);
if (!IsValidExpression<User>(safeExpression))
{
throw new SecurityException("Invalid filter expression");
}
Later I will introduce you to SanitationEngine by PowerCSharp.
2. Use Whitelisting
// Only allow known properties and operations
var allowedProperties = new[] { "Name", "Age", "Status", "CreatedDate" };
var allowedOperations = new[] { "==", "!=", ">", "<", ">=", "<=", "Contains" };
3. Implement Rate Limiting
// Prevent abuse with rate limiting
if (!_rateLimiter.IsAllowed(user.Id))
{
throw new RateLimitExceededException("Too many requests");
}
4. Log Security Events
// Log suspicious activity
if (IsSuspiciousExpression(expression))
{
_logger.LogWarning("Suspicious dynamic LINQ expression: {Expression} from user {UserId}",
expression, user.Id);
}
Common Pitfalls to Avoid
1. Don’t Allow Arbitrary Code Execution
// BAD: Allows method calls
string malicious = "File.Delete(\"important.txt\")";
// GOOD: Only property access and basic operations
string safe = "Age > 25 && Name.Contains(\"John\")";
2. Don’t Ignore Type Safety
// BAD: Type mismatch at runtime
string invalid = "Age > \"twenty\""; // Runtime error
// GOOD: Type-safe validation
if (!IsValidTypeExpression<User>(expression))
{
throw new ArgumentException("Invalid expression");
}
3. Don’t Forget About Null Handling
// BAD: Null reference exceptions
string problematic = "Name.Length > 5"; // Fails if Name is null
// GOOD: Null-safe expressions
string safe = "Name != null && Name.Length > 5";
Monitoring and Observability
Dynamic LINQ systems need monitoring:
Performance Metrics
public class DynamicLinqMetrics
{
public int ExpressionCacheHitCount { get; set; }
public int ExpressionCacheMissCount { get; set; }
public double AverageExecutionTime { get; set; }
public int SecurityViolationCount { get; set; }
}
Health Checks
public class DynamicLinqHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
// Test basic functionality
var testExpression = "Id > 0";
var result = testExpression.GetExpressionDelegate<TestEntity>();
return Task.FromResult(HealthCheckResult.Healthy());
}
catch (Exception ex)
{
return Task.FromResult(HealthCheckResult.Unhealthy("Dynamic LINQ failed", ex));
}
}
}
Conclusion
Dynamic LINQ doesn’t have to be a security nightmare or performance bottleneck. With proper validation, caching, and monitoring, it can be a powerful tool for building flexible, user-friendly applications.
PowerCSharp’s approach balances flexibility with security, making dynamic LINQ production-ready for enterprise applications. The key is to treat user input as potentially malicious while providing the flexibility users need.

PowerCSharp — Enhanced C# extension methods and utilities for .NET developers
Try PowerCSharp’s dynamic LINQ:
dotnet add package PowerCSharp.Extensions
GitHub: github.com/marioarce/PowerCSharp Documentation: Dynamic LINQ Guide
메타데이터
- post_id
- 79702cc5fbca
- slug
- dynamic-linq-in-production-how-i-built-runtime-query-parsing-that-doesnt-suck-79702cc5fbca
- url
- https://medium.com/@marioarce/dynamic-linq-in-production-how-i-built-runtime-query-parsing-that-doesnt-suck-79702cc5fbca
- canonical_url
- https://medium.com/@marioarce/dynamic-linq-in-production-how-i-built-runtime-query-parsing-that-doesnt-suck-79702cc5fbca
- author_url
- https://medium.com/@marioarce
- status
- ok
- fetched_at
- 2026-06-18 07:02:39