Доступ к членам выражения выражения для построения деревьев выражения - PullRequest
0 голосов
/ 11 февраля 2019

Попытка построить заказ по выражению, используя деревья выражений.Но я не могу получить доступ к свойству выражения выражения класса результата запроса.Это структура класса:

public class AssetFileRecord : IAuditable, IEntity, INavigateToCustomValues
{
    public AssetFileRecord()
    {
        this.UpdatedTimeStamp = DateTime.UtcNow;
    }

    public AssetFileRecord GetRecord()
    {
        return this;
    }

    public Guid Id { get; set; }
    public int DisplayId { get; set; }
    public string AssetTagNumber { get; set; }
    [JObjectIgnore]
    public virtual Account Account { get; set; }
    public string AccountNumber => Account?.AccountNumber;
    public string AuditTrail { get; set; }
    public string OldTagNumber { get; set; }
    public ActivityCode ActivityCode { get; set; }

    [JObjectIgnore]
    public virtual ICollection<AssetFileRecordDepreciation> AssetFileRecordDepreciations { get; set; }
    // Depreciation Records
    public double? AccumulatedDepreciation => Depreciation()?.AccumulatedDepreciation;
    public DateTime? DepreciationAsOfDate => Depreciation()?.DepreciationAsOfDate;
    public double? LifeMonths => Depreciation()?.LifeMonths;
    public double? DepreciationBasis => Depreciation()?.DepreciationBasis;
    public double? PeriodDepreciation => Depreciation()?.PeriodDepreciation;

    private AssetFileRecordDepreciation Depreciation()
    {
        return AssetFileRecordDepreciations?.AsQueryable()?.OrderBy(d => d.AssetFileDepreciationBook.BookNo)?.FirstOrDefault();
    }
}

Мне не удается добраться до свойства AccountNumber, которое является свойством виртуального свойства AssetFileRecord.

Ниже приведен текущий код, который отлично работает длялюбые другие свойства без выражения.

var type = typeof(T);
var property = type.GetProperty(sortProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var typeArguments = new[] { type, property.PropertyType };
var methodBase = isFirstOrderTerm ? "OrderBy" : "ThenBy";
var methodName = sortOrder == ListSortDirection.Ascending ? methodBase : $"{methodBase}Descending";
var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(orderByExp));

return source.Provider.CreateQuery<T>(resultExp);

Expression.Call не оценивает допустимый запрос SQL, а скорее выдает исключение.

((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql = '((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql' threw an exception of type 'System.NotSupportedException'

Предполагаемый результат: к нему следует добавитьупорядочить по выражению в дереве выражений, сгенерированном в конце;в то время как это не удается сделать, при попытке заказа с помощью члена свойства выражения bodied.

Может кто-нибудь, пожалуйста, помогите мне заставить это работать.

1 Ответ

0 голосов
/ 11 февраля 2019

У вас две проблемы с вашим подходом.Во-первых, вы не можете использовать нулевой распространяющий оператор в выражениях Linq.Протестируйте этот код:

var account = new Account();
// will cause "error CS8072: An expression tree lambda may not contain a null propagating operator"    
Expression<Func<string>> accountNumber = () => account?.AccountNumber;

Вторая и главная проблема заключается в том, что ваш AccountNumber будет скомпилирован в метод get_AccountNumber, и вы не можете вызывать произвольные методы с Linq to SQL.Вы можете протестировать этот код:

public class AssetFileRecord
{
  //...
  public string AccountNumber => Account != null ? Account.AccountNumber : null;
}

, хотя это может быть скомпилировано, оно выдает то же исключение времени выполнения.

Один из возможных способов обойти эту проблему - создать карту с выражениями для сложных свойств.:

var map = new Dictionary<string, Expression>
{
    {
        "AssetFileRecord.AccountNumber", // type and property
        (Expression<Func<AssetFileRecord, string>>) (
            afr => afr.Account != null ? afr.Account.AccountNumber : null
        )
    }
};

Теперь вы можете переписать свой метод, который строит динамически OrderBy относительно этой карты:

private static IQueryable<T> DynamicOrderBy<T>(
    IQueryable<T> source,
    string sortProperty,
    Dictionary<string, Expression> map)
{
    var type = typeof(T);
    var parameter = Expression.Parameter(type, "p");
    var property = type.GetProperty(sortProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

    Expression whereLambda;
    if (!map.TryGetValue($"{type.Name}.{sortProperty}", out whereLambda))
    {
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        whereLambda = Expression.Lambda(propertyAccess, parameter);
    }
    // else we just using a lambda from map

    // call OrderBy
    var query = Expression.Call(
        typeof(Queryable),
        "OrderBy",
        new[] {type, property.PropertyType},
        source.Expression,
        whereLambda
    );

    return source.Provider.CreateQuery<T>(query);
}
...