Reputation: 7140
I have a data access class that acts as an intermediary between logic classes and the underlying datasource, which is interchangeable. This class allows you to query the datasource using lambdas, LINQ-style. A source-agnostic class provides high-level functionality powered by a few basic operations (Add, GetAll, Update, Delete, Commit) that are implemented by small adapter classes, one for each source type (SQL, SQlite, XML serialiser, WCF client, REST client, whatever).
My problem is that some relational data sources (particularly SQLite) aren't smart enough to load relationship properties when I need them; I have to explicitly ask for them to be included. This is fine for my Get
methods; I can pass a params
array of expressions to load anything I need. With .Any()
, however, this feels a bit odd - if I'm asking if there are any Customer
records whose Purchases
list contains a certain item, I shouldn't then have to tell it to load the Purchases
list; that seems like the sort of thing it should be able to figure out.
So my Any()
method takes Expression<Func<T, bool>>
where T
is obviously going to be the type I'm operating on. In the above example, it'd be used something like this:
using (var db = _dataAccessProvider.NewTransaction())
{
return db.Any<Customer>(c => c.Purchases.Contains(someProduct));
}
Is it possible to take the Expression<Func<Customer, bool>>
that represents the operation c => c.Purchases.Contains(someProduct))
and work out that the property it's referring to is c => c.Purchases
? How would I go about doing that? What about a lambda that touches multiple properties?
Upvotes: 1
Views: 1572
Reputation: 34407
Use ExpressionVisitor
to find all MemberExpression
expressions which reference required object properties.
Quick example:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
class Program
{
sealed class ReferencedPropertyFinder : ExpressionVisitor
{
private readonly Type _ownerType;
private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();
public ReferencedPropertyFinder(Type ownerType)
{
_ownerType = ownerType;
}
public IReadOnlyList<PropertyInfo> Properties
{
get { return _properties; }
}
protected override Expression VisitMember(MemberExpression node)
{
var propertyInfo = node.Member as PropertyInfo;
if(propertyInfo != null && _ownerType.IsAssignableFrom(propertyInfo.DeclaringType))
{
// probably more filtering required
_properties.Add(propertyInfo);
}
return base.VisitMember(node);
}
}
private static IReadOnlyList<PropertyInfo> GetReferencedProperties<T, U>(Expression<Func<T, U>> expression)
{
var v = new ReferencedPropertyFinder(typeof(T));
v.Visit(expression);
return v.Properties;
}
sealed class TestEntity
{
public int PropertyA { get; set; }
public int PropertyB { get; set; }
public int PropertyC { get; set; }
}
static void Main(string[] args)
{
Expression<Func<TestEntity, int>> expression =
e => e.PropertyA + e.PropertyB;
foreach(var property in GetReferencedProperties(expression))
{
Console.WriteLine(property.Name);
}
}
}
Upvotes: 2