Структура базы не имеет значения кроме того, что Id имеет тип Guid.
Фишка в том, что тулкит кеширует экспрешены. В C# id1 и id2 превращаются в замыкания. В немерле в константы. Тулкит по каким-то своим причинам не различает константы типа Guid (IsConstant для Guid возвращает false). Поэтому он считает экспрешены эквивалентными и использует запрос сгенеренный для первой выборки во второй.
Прежде чем постить баг в тулкит хотелось бы понять, правильно ли ведет себе генератор эеспрешенов в Nemerle.Linq.
Здравствуйте, VladD2, Вы писали:
VD>Ну, или как-то по другому сделай так чтобы можно было воспроизвести ошибку на чужой машине.
using System;
using System.Console;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using Nemerle.Data.Linq;
using BLToolkit.Data;
using BLToolkit.Data.Linq;
public class Data
{
public Id : Guid {get;set;}
}
module Program
{
Main() : void
{
def toExpr(e : Expression[Func[Data, bool]]) { e }
def id1 = Guid.NewGuid();
def id2 = Guid.NewGuid();
def e1 = toExpr(d => d.Id == id1);
def e2 = toExpr(d => d.Id == id2);
// корень проблемы:
WriteLine($"Тулкит думает, что $e1 == $e2");
// продемонстрировать напрямую не получается, потому, что метод сравнения в internal классе
// ExpressionHelper.Compare(e1, e2) вернет true, поскольку различаются они только значением константы,
// а тулкит по каким-то причинам не сравнивает значения констант для Guid (скорее всего такая ситуация не встречается в C#)
// воспроизведение:
// Нужен sqlserver и база в которой запущено create table Data (Id uniqueidentifier)
DbTest(@"data source=.\SQLEXPRESS;Integrated Security=true;database=TestDb");
}
DbTest(connString : string) : void
{
using (conn = SqlConnection(connString), db = DbManager(conn))
{
_ = db.BeginTransaction(); // rollbackdef d1 = Data(); d1.Id = Guid.NewGuid();
def d2 = Data(); d2.Id = Guid.NewGuid();
_ = db.Insert(d1);
_ = db.Insert(d2);
def id1 = d1.Id; // если в експрешен отдать d1.Id константы не будет и тест пройдетdef id2 = d2.Id;
_ = db.GetTable.[Data]().Single(d => d.Id == id1);
// вот тут тулкит считает, что ему передали тот же самый экспрешен и выполняет закешированный запросdef test = db.GetTable.[Data]().Single(d => d.Id == id2);
if (test.Id == d2.Id) // test.Id в данном случае будет равен d1.Id
WriteLine("Ok")
else
WriteLine("Error");
}
}
}
Здравствуйте, Ziaw, Вы писали:
Z>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.
А нельзя вместо SqlConnection подсунуть фейковый провайдер? ну или провайдер, использующий csv файлы?
Здравствуйте, Ziaw, Вы писали:
Z>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.
ОК. Попробуй в своем примере сделать следующий изврат...
Создай левый класс. Скажем:
class Fake
{
public GuidValue : Guid { get; set; }
}
Здравствуйте, VladD2, Вы писали:
VD>Если это сработает, то в принципе ясно в чем причина.
Сработает.
Причина мне была ясна до написания первого поста. Просто я видимо ее плохо описал. Попробую еще раз:
Nemerle.Linq при генерации экспрешенов превращает локальные переменные в Expression.Constant (возможно в частных случаях), а C# генерит замыкания.
Тулкит почему-то считает две константы одинаковыми если их тип не входит в некий перечень типов, Guid туда не входит. Поэтому два экспрешена, различающиеся только значением константы, он тоже считает одинаковыми.
Перед созданием запроса из экспрешена тулкит проверяет, не лежит ли в кеше запрос из такого же экспрешена. Если лежит он его использует, проблема в том, что он считает одинаковыми реально отличающиеся экспрешены.
Вопрос что лучше править, тулкит или Nemerle.Linq?
Здравствуйте, seregaa, Вы писали:
Z>>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме. S>А нельзя вместо SqlConnection подсунуть фейковый провайдер? ну или провайдер, использующий csv файлы?
Тулкит умеет только с определенным списком СУБД работать. Генерация запросов сильно завязана на конкретные диалекты SQL.
Здравствуйте, Ziaw, Вы писали:
Z> Nemerle.Linq при генерации экспрешенов превращает локальные переменные в Expression.Constant (возможно в частных случаях), а C# генерит замыкания.
Ага. Проблема в том, что макросы не могут управлять такими тонкими вещами как замыканиями. (Возможно я просто пока что не не знаю как это реализовать.)
Z> Тулкит почему-то считает две константы одинаковыми если их тип не входит в некий перечень типов, Guid туда не входит. Поэтому два экспрешена, различающиеся только значением константы, он тоже считает одинаковыми.
Я говорил с IT по этому поводу. Это он обещал поправить.
Z> Перед созданием запроса из экспрешена тулкит проверяет, не лежит ли в кеше запрос из такого же экспрешена. Если лежит он его использует, проблема в том, что он считает одинаковыми реально отличающиеся экспрешены.
Это его баг, но конечно по уму и Nemerle.Linq нужно научить делать замыкания и ссылаться на его поля. Проблема в том, что это не штатное использование макросов, а значит сложно. Возможно что-то и получится если немного докрутить компилятор.
Возможно получится сделать некий АПИ для работы с переменными которые в последствии могут превратиться в замыкания.
К сожалению преобразование функций в замыкания производится на поздних стадиях компиляции. Когда отрабатывают макросы никакой информации о замыканиях еще нет. Кроме того не ясно как сослаться на объект замыкания. Каким-то образом нужно уметь получить ссылку (this) на объект замыкания, чтобы породить примерно такой код (псевдокод):
| PExpr.Ref(nm) as expr =>
match (lparms.Filter(_.ContainsKey(nm.Id)))
{
| hd :: _ => hd[nm.Id] // это параметр лямбды!
| _ =>
// Сейчас здесь вот такой код:
// <[ Expression.Constant($(nm : name)) ]>
// А нужно что-то вроде этого:def inst = PExpr.Typed(TExpr.This());
match (expr.TypedObject)
{
| TExpr.LocalRef(decl) =>
def tField = ???;
<[ Expression.Field($inst, $tField : FieldInfo) ]>
| _ => сообщаем об ошибке
}
}
Z>Вопрос что лучше править, тулкит или Nemerle.Linq?
И то, и то.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Я говорил с IT по этому поводу. Это он обещал поправить.
Гут. Я у себя подправил, но форкать (тем более svn) не хотелось бы
VD>Это его баг, но конечно по уму и Nemerle.Linq нужно научить делать замыкания и ссылаться на его поля. Проблема в том, что это не штатное использование макросов, а значит сложно. Возможно что-то и получится если немного докрутить компилятор.
VD>Возможно получится сделать некий АПИ для работы с переменными которые в последствии могут превратиться в замыкания. VD>К сожалению преобразование функций в замыкания производится на поздних стадиях компиляции. Когда отрабатывают макросы никакой информации о замыканиях еще нет. Кроме того не ясно как сослаться на объект замыкания. Каким-то образом нужно уметь получить ссылку (this) на объект замыкания, чтобы породить примерно такой код (псевдокод):
Тут вообще на первый взгляд все как-то сложно:
mutable n = 1;
toExpr(_ => n > 0);
n++;
должно превратиться во что-то типа
def closure = new (n = 1);
closure.n = 1;
Expression.Lambda(... Expression.MemberAccess(closure, "n") ...) // API не помню, но принцип такой
closure.n++;
непонятно что делать в более сложных случаях:
mutable n = 1;
mutable m = 2;
toExpr(_ => n > 0);
toExpr(_ => n > 0 && m > 0);
toExpr(_ => m > 0);
{
mutable m = 3;
toExpr(_ => n > 0 && m > 0);
toExpr(_ => m > 0);
toExpr(_ => m > 0);
}
n++;
m++;
Но шарп такие проблемы как-то решает, надо смотреть как.
Здравствуйте, IT, Вы писали:
IT>Ведёт себя неправильно.
Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.
Здравствуйте, Ziaw, Вы писали:
IT>>Ведёт себя неправильно.
Z>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.
Может компилятор наоптимизировал?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>>>Ведёт себя неправильно.
Z>>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.
IT>Может компилятор наоптимизировал?
Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?
Здравствуйте, Ziaw, Вы писали:
IT>>>>Ведёт себя неправильно. Z>>>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания. IT>>Может компилятор наоптимизировал? Z>Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?
Тогда я не понял к кому вопрос
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
Z>>Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?
IT>Тогда я не понял к кому вопрос
ты сказал, что компилятор ведет себя неправильно. Если это относится к общему неумению генерить замыкания в экспрешенах — вопрос снимается. В примере же константы вместо замыканий мне кажутся вполне логичным поведением.
Z>def closure = new (n = 1);
Z>closure.n = 1;
Z>Expression.Lambda(... Expression.MemberAccess(closure, "n") ...) // API не помню, но принцип такой
Z>closure.n++;
Z>
Ага. Почти так. Только имена будут не такими красивыми, а чем-то вроде
И конечно же лучше имя поля не строкой задавать, а через TExpr.FieldOf().
Вот только макрос работают намного раньше формирования замыканий и соответствующего переписывания кода.
Я вчера пол ночи сидел и смотрел что можно сделать чтобы это обойти, но так ничего и не сделал.
В голову приходит только одно решение — добавить два новых TExpr-шона:
| TExpr.Closure
| TExpr.ClosureвFieldOf { localValue : LocalValue }
Для них придется добавить обработку на поздних стадиях компиляции и генерацию кода. Это не так-то просто.
Но по другому никак (по крайней мере я не вижу как).
Попробую в ближайшее время реализовать. Механизм в общем-то универсальный. Может и в других местах пригодиться.
Z>Но шарп такие проблемы как-то решает, надо смотреть как.
Примерно так как ты описал.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
ты сказал, что компилятор ведет себя неправильно. Если это относится к общему неумению генерить замыкания в экспрешенах — вопрос снимается. В примере же константы вместо замыканий мне кажутся вполне логичным поведением.
Guid в CLR в принципе нельзя объявить константой. Если какая-либо программа учитывает этот факт, то кто будет виноват — компилятор или эта программа?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Ziaw, Вы писали:
Z>Фишка в том, что тулкит кеширует экспрешены. В C# id1 и id2 превращаются в замыкания. В немерле в константы. Тулкит по каким-то своим причинам не различает константы типа Guid (IsConstant для Guid возвращает false). Поэтому он считает экспрешены эквивалентными и использует запрос сгенеренный для первой выборки во второй.
Реализовал поддержку замыканий в Linq-макросе. Так что теперь указанный код должен работать корректно даже со старым БЛТулкит-провайдором (по тестируй... только не забуть пересобрать компилятор и Nwmerle.Linq.dll).
Кроме того теперь будут корректно работать множественное выполнение одного запроса с изменением значений переменных используемых внутри запроса.
ЗЫ
IT вроде как поправил и свой провайдер, так что бага вообще не должно появляться ни при каких условиях.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Ziaw, Вы писали:
Z>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.
Собственно вот чистый тест воспроизводящий проблему:
using System;
using System.Console;
using Nemerle.Extensions;
using System.Linq.Expressions;
class Data
{
public Id : Guid { get; set; }
}
module Program
{
Main() : void
{
def toExpr(e : Expression[Func[Data, bool]]) { e }
mutable id1 = Guid.NewGuid();
mutable id2 = Guid.NewGuid();
def e1 = toExpr(d => d.Id == id1);
def e2 = toExpr(d => d.Id == id2);
def f1 = e1.Compile();
def f2 = e2.Compile();
def d = Data();
d.Id = id1;
WriteLine(f1(d));
WriteLine(f2(d));
id2 = id1;
WriteLine(f1(d));
WriteLine(f2(d));
}
}
Теперь вместо Expression.Constant() для локальных переменных создается Expression.Field(замыкание, поле_из_замыкания), так что проблем быть не должно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, IT, Вы писали:
IT>Guid в CLR в принципе нельзя объявить константой.
Казалось бы причем тут коснтсанты CLR? Речь идет о деревьях выражений в которых Constant — это не более чем захват значений.
IT>Если какая-либо программа учитывает этот факт, то кто будет виноват — компилятор или эта программа?
Пока что единственной программой которая учитывает "факт" — это твой провайдер. Остальные работают корректно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
IT>>Guid в CLR в принципе нельзя объявить константой. VD>Казалось бы причем тут коснтсанты CLR? Речь идет о деревьях выражений в которых Constant — это не более чем захват значений.
Именно формированием дерева выражения в ручную я этот баг и воспроизводил. А при желании ручным формированием деревьев выражений можно завалить любой провайдер. И сказать при это, что это не более чем дерево выражений, при чём тут компилятор.
IT>>Если какая-либо программа учитывает этот факт, то кто будет виноват — компилятор или эта программа? VD>Пока что единственной программой которая учитывает "факт" — это твой провайдер. Остальные работают корректно.
А сколько ты их проверял?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Именно формированием дерева выражения в ручную я этот баг и воспроизводил. А при желании ручным формированием деревьев выражений можно завалить любой провайдер. И сказать при это, что это не более чем дерево выражений, при чём тут компилятор.
Ну, то есть то что все остальные провайдеры ведут себя нормально — это у них баг.
Кроме того нормальным по видимому является и то, что твой провадер вместо того чтобы сообщить пользователю о том, что переданное ему дерево выражений, с его точки зрения не корректно, просто выдает не верный результат.
"Хорошая" позиция.
IT>>>Если какая-либо программа учитывает этот факт, то кто будет виноват — компилятор или эта программа? VD>>Пока что единственной программой которая учитывает "факт" — это твой провайдер. Остальные работают корректно.
IT>А сколько ты их проверял?
Столько сколько сделал Майкрософт.
Вообще, тут, на мой взгляд, даже обсуждать не чего. Учитывая, что провадеры все транслируют в SQL, нужно поддерживать и константы, и ссылки на поля для всех типов поддерживаемых SQL-серверами. Можем сериализовать в SQL? Значит надо поддерживать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>"Хорошая" позиция.
Моя позиция простая — я тихо и без криков признал, что это баг и быстренько его пофиксил. Чем тебя не устраивает такая позиция?
IT>>А сколько ты их проверял? VD>Столько сколько сделал Майкрософт.
Т.е. ровно один — Linq 2 SQL.
VD>Вообще, тут, на мой взгляд, даже обсуждать не чего. Учитывая, что провадеры все транслируют в SQL, нужно поддерживать и константы, и ссылки на поля для всех типов поддерживаемых SQL-серверами. Можем сериализовать в SQL? Значит надо поддерживать.
Так вот, те провайдер(ы), которые ты тестировал в константы вообще ничего не транслируют, а на каждый чих создают параметр. Т.е. если написать table.Field == 1, то в SQL ты получишь table.Field = @param1. Поэтому, у такого провайдера подобного баг быть не может в принципе. Но правильным ли является такой подход? Вот в чём вопрос.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Моя позиция простая — я тихо и без криков признал, что это баг и быстренько его пофиксил. Чем тебя не устраивает такая позиция?
Такая позиция меня устаревает. Не ясно только тогда зачем были нужны высказывания в духе "в ЦЛР нет...".
IT>Так вот, те провайдер(ы), которые ты тестировал в константы вообще ничего не транслируют,
Провайдеры вообще не должны ничего траслировать. Их задача читать деревья выражений и герерить SQL.
IT>а на каждый чих создают параметр. Т.е. если написать table.Field == 1, то в SQL ты получишь table.Field = @param1. Поэтому, у такого провайдера подобного баг быть не может в принципе. Но правильным ли является такой подход? Вот в чём вопрос.
С полями разговор отдельный. Мы ведь говорили о значениях.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
IT>>Моя позиция простая — я тихо и без криков признал, что это баг и быстренько его пофиксил. Чем тебя не устраивает такая позиция? VD>Такая позиция меня устаревает. Не ясно только тогда зачем были нужны высказывания в духе "в ЦЛР нет...".
Потому что это на самом деле так. В CLR нельзя создать константу типа Guid.
IT>>Так вот, те провайдер(ы), которые ты тестировал в константы вообще ничего не транслируют, VD>Провайдеры вообще не должны ничего траслировать. Их задача читать деревья выражений и герерить SQL.
Так вот, те провайдер(ы), которые ты тестировал константы в SQL вообще не генерируют.
IT>>а на каждый чих создают параметр. Т.е. если написать table.Field == 1, то в SQL ты получишь table.Field = @param1. Поэтому, у такого провайдера подобного баг быть не может в принципе. Но правильным ли является такой подход? Вот в чём вопрос.
VD>С полями разговор отдельный. Мы ведь говорили о значениях.
'1' — это и есть значение, которое L2S преобразует в параметр.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Потому что это на самом деле так. В CLR нельзя создать константу типа Guid.
Несомненно. Только к делу не относится.
IT>Так вот, те провайдер(ы), которые ты тестировал константы в SQL вообще не генерируют. IT>...'1' — это и есть значение, которое L2S преобразует в параметр.
В этом есть свой резон. Но опять же мо не об этом говорим.
ЗЫ
Вдумайся. В шарпе есть всего несколько типов кроторые переврдятся с литералы. Зачем тогда в Expression.Conctant воспользовалисс object-ом, а не сделоло несколько перегрузок, как сделали с функциях вроде Sum?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Вдумайся. В шарпе есть всего несколько типов кроторые переврдятся с литералы. Зачем тогда в Expression.Conctant воспользовалисс object-ом, а не сделоло несколько перегрузок, как сделали с функциях вроде Sum?
Почему object? Хотя бы потому что null — это тоже константа.
Если нам не помогут, то мы тоже никого не пощадим.