Структура базы не имеет значения кроме того, что 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 вроде как поправил и свой провайдер, так что бага вообще не должно появляться ни при каких условиях.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.