Подтвердить что ты не робот

Добавление внутреннего соединения в DbScanExpression в Entity Framework Interceptor

Я пытаюсь использовать перехватчик Entity Framework CommandTree, чтобы добавить фильтр к каждому запросу через DbContext.

Для простоты у меня есть две таблицы, одна из которых называется "Пользователь" с двумя столбцами ( "UserId" и "EmailAddress" ), а другая называется "TenantUser" с двумя столбцами ( "UserId" и "TenantId" ).

Каждый раз, когда есть таблица DbScan для пользователя, я хочу сделать внутреннее соединение с таблицей TenantUser и фильтровать на основе столбца TenantId.

Существует проект под названием EntityFramework.Filters, который делает что-то в этом направлении, но не поддерживает "сложные объединения", что, кажется, является тем, что я пытаясь сделать.

Следуя демо от TechEd 2014, я создал перехватчик, который использует посетителя с приведенным ниже способом для замены DbScanExpressions на DbJoinExpression. Как только я получу эту работу, я планирую обернуть ее в DbFilterExpression, чтобы сравнить столбец TenantId с известным идентификатором.

    public override DbExpression Visit(DbScanExpression expression)
    {
        var table = expression.Target.ElementType as EntityType;
        if (table != null && table.Name == "User")
        {
            return DbExpressionBuilder.InnerJoin(expression, DbExpressionBuilder.Scan(expression.Target), (l, r) =>
                DbExpressionBuilder.Equal(DbExpressionBuilder.Variable(tenantUserIdProperty.TypeUsage, "UserId"),
                    DbExpressionBuilder.Variable(userIdProperty.TypeUsage, "UserId")));
        }

        return base.Visit(expression);
    }

Чтобы проверить код выше, я добавил перехватчик в dbContext и запустил следующий код:

    dbContext.Users.Select(u => new { u.EmailAddress }).ToList();

Однако это приводит к следующей ошибке:

Нет свойства с именем "EmailAddress" объявляется типом "Transient.rowtype [(l, CodeFirstDatabaseSchema.User(Nullable = True, DefaultValue =)), (r, CodeFirstDatabaseSchema.User(Nullable = True, DefaultValue = ))]".

Я неправильно создаю DbJoinExpression? Или я пропущу что-то еще?

4b9b3361

Ответ 1

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

public override DbExpression Visit(DbScanExpression expression)
{
    var table = expression.Target.ElementType as EntityType;
    if (table != null && table.Name == "User")
    {
        return expression.InnerJoin(
            DbExpressionBuilder.Scan(expression.Target.EntityContainer.BaseEntitySets.Single(s => s.Name == "TennantUser")),
            (l, r) =>
                DbExpressionBuilder.Equal(
                    DbExpressionBuilder.Property(l, "UserId"),
                    DbExpressionBuilder.Property(r, "UserId")
                )
        )
        .Select(exp => 
            new { 
                UserId = exp.Property("l").Property("UserId"), 
                Email = exp.Property("l").Property("Email") 
            });
    }

    return base.Visit(expression);
}

Как вы видите после операции соединения, вы ссылаетесь на конкретную связанную таблицу, используя свой псевдоним выражения лямбда из выражения, определяющего условие соединения. Поэтому в моем случае вы ссылаетесь на таблицу пользователя как l и на TennantUser как r. Буквы l и r будут использоваться, а также псевдонимы в результате SQL-запроса, отправленного в базу данных. Между операциями InnerJoin и Select вы можете добавить дополнительную логику, например Filter и т.д.