включая "автономный" код в скомпилированных запросах - PullRequest
6 голосов
/ 07 апреля 2011

Что происходит за кулисами, когда я включаю функцию в свой скомпилированный запрос, как я делаю здесь с DataConvert.ToThema (), чтобы преобразовать табличный объект в мой пользовательский бизнес-объект:

public static class Queries
{
    public static Func<MyDataContext, string, Thema> GetThemaByTitle
    {
        get
        {
            var func = CompiledQuery.Compile(
                (MyDataContext db, string title) =>
                    (from th in elan.tbl_Thema
                     where th.Titel == title
                     select DataConvert.ToThema(th)).Single()
                     );
            return func;
        }
    }
}

public static class DataConvert
{
    public static Thema ToThema(tbl_Thema tblThema)
    {
        Thema thema = new Thema();

        thema.ID = tblThema.ThemaID;
        thema.Titel = tblThema.Titel;
        // and some other stuff

        return thema;
    }
}

и вызватьэто так

Thema th = Queries.GetThemaByTitle.Invoke(db, "someTitle");

Видимо, функция не переводится в SQL или что-то (как это может), но она также не сохраняется, когда я устанавливаю точку останова в VS2010.

Работает без проблем, но я не понимаю, как и почему.Что именно там происходит?

Ответы [ 2 ]

2 голосов
/ 10 апреля 2011

Ваш DataConvert.ToThema() статический метод просто создает экземпляр типа, который имеет конструктор по умолчанию, и устанавливает различные свойства, это правильно? Если так, это не сильно отличается от:

(from th in elan.tbl_Thema
where th.Titel == title
select new Thema{ID=th.ThemaID, Titel=th.Titel, etc...}
).Single());

Когда вы звоните Queries.GetThemaByTitle, запрос компилируется. (Кстати, то, как вы это называете, может на самом деле давать или не давать вам какие-либо преимущества от предварительной компиляции). Этот «запрос» на самом деле является деревом выражений кода, только часть которого предназначена для генерации кода SQL, отправляемого в базу данных.

Другие его части будут генерировать код IL, который захватывает то, что возвращается из базы данных, и переводит его в какую-то форму для вашего потребления. LINQ (EF или L2S) достаточно умен, чтобы иметь возможность принимать ваш статический вызов метода и генерировать из него IL, чтобы делать то, что вы хотите - и, возможно, он делает это с внутренним delegate или чем-то подобным. Но, в конечном счете, он не должен (сильно) отличаться от того, что будет сгенерировано из того, что я подставил выше.

Но учтите, что это происходит независимо от того, какого типа вы получаете; где-то генерируется код IL, который помещает значения БД в объект CLR. Это другая часть этих деревьев выражений.


Если вы хотите более детально взглянуть на эти деревья выражений и на то, что с ними связано, я бы попросил вас, но я не уверен по вашему вопросу, действительно ли это то, что вы ищете.

1 голос
/ 15 апреля 2011

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

Технически, как отметил Эндрю, сделать эту работу не так уж сложно. Когда ваше выражение LINQ оценивается, дерево выражений создается внутренне. Ваша функция отображается как узел в этом дереве выражений. Никакой магии здесь. Вы сможете написать это выражение как в L2S, так и в L2E, и оно будет компилироваться и работать нормально. Это происходит до тех пор, пока вы не попытаетесь фактически выполнить фактический запрос SQL к базе данных. Вот где начинается разница. L2S, похоже, успешно выполняет эту задачу, тогда как L2E завершается с ошибкой NotSupportedException и сообщает, что не знает, как преобразовать ToThema в запрос хранилища.

Так что же происходит внутри? В L2S, как объяснил Эндрю, компилятор запросов понимает, что ваша функция может быть запущена отдельно от того, как был выполнен запрос хранилища. Таким образом, он отправляет вызовы вашей функции в конвейер чтения объектов (где данные, прочитанные из SQL, преобразуются в объекты, возвращаемые в результате вашего вызова).

Когда-то Эндрю не совсем прав, это то, что важно то, что находится внутри вашего статического метода. Я не думаю, что это так.

Если вы поставите точку останова в отладчике для своей функции, вы увидите, что она вызывается один раз для возвращаемой строки. В трассировке стека вы увидите «облегченную функцию», которая в действительности означает, что метод был запущен во время выполнения. Так вот как это работает для Linq to Sql.

Команда Linq to Entity, похоже, шла другим путем. Я не знаю, в чем причина, почему они решили запретить все Invocation Expressions из запросов L2E. Возможно, это было связано с производительностью или может быть связано с необходимостью поддержки всех типов поставщиков, а не только SQL Server, чтобы устройства чтения данных могли вести себя по-разному. Или они просто подумали, что большинство людей не поймут, что некоторые из них выполняются для каждой возвращаемой строки, и предпочли оставить эту опцию закрытой.

Только мои мысли. Если у кого-то есть еще понимание, пожалуйста, присоединяйтесь!

...