last day (15 days later) » 

10:11
0
Q: Не могу найти причину, по которой "что-то" изменяет содержимое SQLResult

ArtemSQLQueryHolder<R> m_holder; Dictionary<T, PreparedStatement> m_queries = new(); Dictionary<T, SQLResult> _results = new(); public bool Execute<T>(MySqlBase<T> mySqlBase) { if (m_holder == null) return false; // execute all queries in the holder and pass the results foreach (...

Проблема в вашем коде. Точка. По крайней мере одну логическую ошибку я вижу: метод NextRow возвращает булево значение, но оно не проверяется в месте вызова (в конструкторе SQLResult). Если этот метод вернул false, то дальнейшая работа с этим экземпляром SQLResult бессмысленна.
Метод Query в блоке catch вернёт new SQLResult() - опять-таки, дальнейшая работа с этим экземпляром бессмысленна. Ведь _reader внутри будет равен null и обращение к любым методам должно бросать NRE.
_connectionInfo.GetConnection() создаёт новое соединение или берёт откуда-то уже готовое? Если последнее, все экземпляры SQLResult будут использовать одно и то же соединение и опция CommandBehavior.CloseConnection закроет это соединение, когда любой из них завершён. И работа остальных нарушится.
В общем, вопросов по коду много, даже при беглом осмотре. / Я бы посоветовал не держать соединение открытым долго. Подключились к БД, считали нужные данные в коллекции, закрыли соединение.
Глянул тему на гитхабе. Ну, собственно, там те же претензии к коду.
Один участник проекта сообщил, что там были изменения с кэшированием - не выдумывайте. Это вы у себя кэшируете ридер. Он это и спрашивает: зачем вы это делаете?
Ну что там, есть подвижки?
@AlexanderPetrov Простите, только увидел что вы дали ответ. _connectionInfo.GetConnection() - создает новое соединение, я сделал так чтобы просто давало строку подключения, и вызываю создание уже по месту вызова. Думаю так правильнее, но это ничего не меняет. У меня создается в методе Query каждый раз новое подключение, но не закрывается до тех пор пока сам результат не будет уничтожен. Не вижу проблем с тем когда это произойдет, так как приложение работает в синхронном режиме с БД. Конечно у меня вопрос - "можно ли пользоваться результатом после закрытия соединения"?
Переделал код, сделал SQLResult как IDisposable и пользуюсь только через using, но опять же это лишь решает проблему с переизбытком подключений, так как на каждый ответ свое подключение по сути. Ничего не поменялось по сути. SQLQueryHolder<R> m_holder; - хранит ответы, а значит и подключения созданные для этих ответов. Тоже реализовал ему IDisposable чтобы явно закрывал соединения. Опять же - на что это влияет? Если у меня на каждую транзакцию новое подключение - что может повлиять на мою коллекцию. Тут либо что-то меняет коллекцию, либо идет перезапись MySQLResult на уровне библиотеки.
Но я повторюсь - код прекрасно работает на старой библиотеке. А значит что идет перезапись MySQLResult на уровне библиотеки. Учитывая что кажлый MySQLResult имеет свое уникальное подключение - я не пойму что именно может заставить его перезаписаться. Причем всегда изменения одинаковые - это копия следующего в коллекции MySQLResult в обвертке SQLResult
По поводу NextRow(); в конструкторе SQLResult - я вообще без понятия зачем его там вызывать, думаю что первая строка содержит какую-то ненужную инфу, но если _reader будет null ничего страшного не произойдет - все проверки содержат проверку на null во всех методах обвертки SQLResult. Кроме этого проблема то не в обращении к null - я бы словил это исключение сразу же и устранил эту глупую ошибку. Вообще я заметил, что людям главное увидеть тупой косяк, не относящийся к проблеме никаким образом и поставить крест на всем остальном. Этот иностранец дал ссылку на сайт библиотеки но там инфы 0
Главный вопрос для меня "Можно ли пользоваться результатами при закрытом/уничтоженном соединении" , почему нет если это просто данные в оперативке. Но при этом тот кто писал код явно задает параметр System.Data.CommandBehavior.CloseConnection будто боясь закрывать соединение сразу. Не пойму логики, отдал результат, пометь внутри что соединение закрыто да и все на этом. Второй вопрос - почему иногда используется cmd.Execute, а иногда cmd.ExecuteImmediately - или что-то типо того. На странице библиотеки показано использование 3х методов через юзинг и больше никаких примеров и пояснений, ппц
_connectionInfo.GetConnection() - создает новое соединение - хорошо, принято.
По поводу NextRow(); - он критически важен: внутри него вызывается _reader.Read() - без него вообще ничего работать не будет.
Вообще я заметил, что людям главное увидеть тупой косяк, не относящийся к проблеме никаким образом и поставить крест на всем остальном. - но косяк же есть! Возможно, после его устранения проблема исчезнет. Сторонний наблюдатель не видит весь код, поэтому не может знать, что влияет.
"Можно ли пользоваться результатами при закрытом/уничтоженном соединении" , почему нет если это просто данные в оперативке - но данных нет в оперативке! Они вычитываются из ридера, из самой базы данных, а для этого должно быть открытое соединение.
В общем я уловил мыслю этого парня. Он указал что раньше на 1 соединение был 1 ридер. И типо даже если ты удалил ридер и он еще небыл убран уборщиком мусора - им можно было еще пользоваться и никаких ошибок небыло, а теперь будет ObjectDisposedException. Объект ридера уничтожается только после уничтожения SQLResult и то, в самом финализаторе, к которому он прикопался, что по сути ничего не меняет, так как финализатор вызывается только уже при сборке мусора, туда он может попасть только если на него ничего не ссылается. А значит что там с этим ридером будет мне вообще всеравно.
Я добавил реализацию диспосабл чтобы якобы убрать утечку данных, о которой писал иностранец, но по сути никакой утечки не будет, ведь ридер является обверткой над неуправляемым ресурсом и он всеравно освободит его, тут просто вопрос времени и все!
Не видя весь код я лишь могу продолжить гадать. Ошибка произошла в TrySetResult. Что это за метод, кем написанный: ваш собственный или из сторонней либы? Вроде как тут замешан TaskCompletionSource, но зачем - хз. Но явно заюзана ручная асинхронность.
10:11
"но данных нет в оперативке! Они вычитываются из ридера, из самой базы данных, а для этого должно быть открытое соединение" - так правильно, как я понял там идет асинхронная операция чтения, то есть ссылка возвращается на данные, но самих данных там еще может не быть. Однако сам метод _Read() наверняка ждет внутри выпонение этой асинхронной операции, и блокирует выполнение кода пока там ответ не появится. Тоже самое с TaskCompletionSource<>.TrySetResult() - он ждет пока асинхронная операция не завершися - а значит когда данные получены уже - зачем держать соединение вообще
Да, я согласен, я не умею задавать вопросы, потому что сам не знаю откуда копать и что важно а что нет. Спасибо за помощь, читая ответы я хотя бы задаю себе наводящие вопросы в голове
зачем держать соединение вообще - Именно! Зачем вы держите соединение?
Задавать хорошие вопросы - это искусство, согласен. Я тоже этого не умею.
Должны ли методы Read<T>, IsNull, IsEmpty быть публичными? Вызываются они где-то извне? Или достаточно публичного GetFields или ReadValues<T>?
Проблема устраняется элементарно, если использовать стандартный сценарий работы с БД: открываем соединение, читаем данные, заносим их в коллекцию, возвращаем эту коллекцию. Соединение и ридер при этом диспозятся. Всё, далее работаем с набором данных в оперативке.
Суть в том, что вы можете обидется, потому что вопрос касается не чего-то серьезного, не по работе, а для меня в качестве хобби и изучения. Я создал форк игрового сервера и пытаюсь делать ему рефакторинг. Это порт с С++ на С# , но на С++ было все тоже самое. Все методы public, все классы используют все классы напрямую, минимум комментариев, везде магические числа (возможно защита от ответственности, потому что сделать сервер без дизасемблирования нельзя и может быть явно ничего не указывается нарочно). Но вот что касается библиотек - там все было грамотно сделано, я больше багов добавлю наверн
открываем соединение, читаем данные, заносим их в коллекцию, возвращаем эту коллекцию. Соединение и ридер при этом диспозятся - я думал этого не делают по причине "зачем копировать тонны данных если он уже висят в ридере". Тут вопрос не к вам лично а к библиотеке. Зачем заставлять пользователя копировать данные, если можно просто отдать ему ссылку на них, обнулить у себя эту ссылку и дальше использовать свой пулл. Вот в чем вопрос, который меня гложит. Будто была такая задумка, но вы говорите что так никто не делает
Так, я не обратил внимание сразу, что метод NextRow() тоже публичный. Значит считывается не единственная строка, как я думал, а потенциально много. Тогда некоторый смысл есть. Но тут вопрос: тонны данных - это сколько? Если гигабайты, то это одно, а если всего несколько десятков строк по несколько килобайт - совсем другое.
Почему работало в старой версии библиотеки и не работает в новой? Это необязательно означает, что в новой версии ошибка. Просто теперь данные и код располагаются в памяти иначе при компиляции, отсюда может возникнуть другое поведение в многопоточном/асинхронном коде, если где-то неправильная синхронизация.
@AlexanderPetrov была дырка в библиотеке, которую использовали, дырку закрыли - работа упала :)
10:29
Если гигабайты, то это одно, а если всего несколько десятков строк по несколько килобайт - совсем другое. - ну просто это логично, было бы, в любом случае. Конкретно в данном примере, где используется TaskCompletionSource - это при загрузке сервера загружается вся БД.
Создается целая куча запросов, а затем скопом посылается запрос. Это еще нужно будет разобраться зачем им создавать потоки. На первый взгляд кажется, что сервер заточено на то, что база данных разделена на 3 части и они могут находится на разных серверах.
Под каждую часть создается свой поток, который просто в обычном режиме делает запросы каждый к своей базе, как указано в коде выше. И это все сделано только для того чтобы скопом оформить все запросы и потом поочередно их запулить и собрать от веты в коллекцию. Так вот когда эти ответы приходят - все норм. А потом данные сами по себе там меняются на соседние. Ну я хотя бы понял где копать, спасибо.

last day (15 days later) »