Dillon Drobena
Dillon Drobena

Reputation: 921

How can I rewrite this LINQ query with reflection

So I had written this LINQ query using reflection, and later found out it isn't supported. What would be the best way to get the same functionality from this code?

List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>().Where(p => typeof(Profile)
.GetProperty(handler.Name + "UUID").GetValue(p) == obj.uuid).ToListAsync();

Upvotes: 1

Views: 342

Answers (2)

Jon Hanna
Jon Hanna

Reputation: 113282

Use the reflection to create the query, not in the query. Consider:

public static IQueryable<Profile> Filter(
  this IQueryable<Profile> source, string name, Guid uuid)
{
  // .<name>UUID
  var property = typeof(Profile).GetProperty(name + "UUID");

  // p
  var parExp = Expression.Parameter(typeof(Profile));

  // p.<name>UUID
  var methodExp = Expression.Property(parExp, property);     

  // uuid
  var constExp = Expression.Constant(uuid, typeof(Guid));    

  // p.<name>UUID == uuid
  var binExp = Expression.Equal(methodExp, constExp);                  

  // p => p.<name>UUID == uuid
  var lambda = Expression.Lambda<Func<Profile, bool>>(binExp, parExp);

  // source.Where(p => p.<name>UUID == uuid)
  return source.Where(lambda);
}

This builds up the expression first (so if name was "Test" it would create the expression corresponding with p => p.TestUUID == uuid and then uses that in the call to Where.

Because this step is done first, rather than within the expression itself, there's no need for the query engine to try to translate typeof or GetProperty() into SQL (which it of course, couldn't do).

So:

var filtered = MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid);

Returns an IQueryable<Profile> with the appropriate Where attached. And so:

var profilesFromUUID = await MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid).ToListAsync();

Will as a whole first use reflection to build the query, then apply the query, then produce a list from it asynchrously and then wait for its results.

It's worth noting that since Filter() will accept any IQueryable<Profile> they can be either chained or unioned. So:

MobileService.GetTable<Profile>().Filter("A", uuid0).Filter("B", uuid1);

Is equivalent to:

from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 && p.BUUID == uuid1

And:

MobileService.GetTable<Profile>().Filter("A", uuid0).Union(
  MobileService.GetTable<Profile>().Filter("B", uuid1))

Is equivalent to:

from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 || p.BUUID == uuid1

A more generalised version would be:

public static IQueryable<TSource> FilterByNamedProperty<TSource, TValue>(this IQueryable<TSource> source, string propertyName, TValue value)
{
  var property = typeof(TSource).GetProperty(propertyName);
  var parExp = Expression.Parameter(typeof(TSource));
  var methodExp = Expression.Property(parExp, property);
  var constExp = Expression.Constant(value, typeof(TValue));
  var binExp = Expression.Equal(methodExp, constExp);
  var lambda = Expression.Lambda<Func<TSource, bool>>(binExp, parExp);
  return source.Where(lambda);
}

Then while you have to do the + "UUID" in the calling code, you can use this to do analogous queries with any IQueryable<> of any element type.

Upvotes: 1

Eric
Eric

Reputation: 5743

How about just compare all property name? By definition UUID would not have collision anyway. Since Profile is just a data class, the # of the property for UUID is fixed.

List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
    .Where(p => 
        p.A_UUID == obj.uuid || 
        p.B_UUID == obj.uuid ||
        p.C_UUID == obj.uuid)
    .ToListAsync();

Or add a method (extension method) for Profile like:

public static Guid GetUUIDByTableName(this Profile value, string tableName)
{
    switch (tableName)
    {
        case "A_": return value.A_UUID;
        case "B_": return value.B_UUID;
        default: return Guid.Empty;
    }
}


List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
    .Where(p => p.GetUUIDByTableName(handler.Name) == obj.uuid)
    .ToListAsync();

Upvotes: 1

Related Questions