Форум программистов, компьютерный форум, киберфорум
C#: Базы данных, ADO.NET
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.57/14: Рейтинг темы: голосов - 14, средняя оценка - 4.57
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
1

EF: странности при повторном подключении

04.08.2022, 14:38. Показов 2780. Ответов 22
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Есть обычный EF-контекст (NET6).
C#
1
var someContext = new SomeContext(options);
Для которого далее выполняется:
C#
1
someContext.Database.OpenConnection();
Ок, работает. При указании корректной строки подключения, подключение выполняется.

Далее ситуация - SQL-сервер не доступен:
Код
An error has occurred while establishing a connection to the server (code: 2)
Тоже ок, 'OpenConnection' выдаёт корректный экземпляр 'SqlException', когда истекает таймаут ожидания подключения (указанный в строке подключения).

Далее, после обработки исключения, повторяется полностью аналогичный набор действий:
1. Создать новый экземпляр контекста (явным образом, т.е. тут нет никаких наводок от DI-контейнера) с той же строкой подключения.
2. Попытаться открыть подключение.

Всё тоже ок, второй пункт корректно выдаёт такое же исключение.

Однако, вторая попытка подключения выдаёт обозначенное выше исключение сразу же, т.е. вообще без таймаута.

На всякий случай вывел в логи хэш самого контекста, хэш коннекта 'someContext.Database.GetDbConnection().GetHashCode()'. Разные, т.е. это точно полностью новый экземпляр и контекста, и самого коннекта к базе.

Почему может быть такое поведение? Я предполагал, что каждый коннект будет отрабатывать независимо, и у каждого будет свой таймаут.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
04.08.2022, 14:38
Ответы с готовыми решениями:

USB устройство не определяется при повторном подключении
Здравствуйте, гуру!) Подскажите пожалуйста, столкнулся с проблемой на своем Mac Book Air El...

При повторном подключении колонок - звука нет
Добрый день, уважаемые эксперты! Имеется windows8, имеются колонки. Они были включены в заднее...

Пропадает звук при повторном подключении наушников
Доброго времени суток! Имеем исходные данные: Ноутбук Lenovo V310 Комбинированный аудио разъем...

Ошибка при повторном подключении к FTP и заливке
Вот такой вот код отправляет файл на ФТП - но при повторном нажатии на кнопку - вылетает ошибка ...

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

22
3462 / 2473 / 695
Регистрация: 02.08.2011
Сообщений: 6,704
04.08.2022, 15:49 2
Лучший ответ Сообщение было отмечено kotelok как решение

Решение

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

Добавлено через 48 минут
Для EFCore, в зависимости от СУБД разные провайдеры используются (ежу понятно).
Для Sql Server есть сборка Microsoft.EntityFrameworkCore.SqlServer.dll, где есть тип SqlServerConnection (реализация IRelationalConnection), в котором есть реализация метода:
C#
1
2
3
4
5
6
7
protected override void OpenDbConnection(bool errorsExpected)
    {
      if (errorsExpected && this.DbConnection is SqlConnection dbConnection)
        dbConnection.Open(SqlConnectionOverrides.OpenWithoutRetry);
      else
        this.DbConnection.Open();
    }
Видимо, первый раз retry идет, а последующие уже без него. Сам метод вызывается вообще в расширении для DatabaseFacade в коде DbContext-а (RelationalDatabaseFacadeExtensions класс из Microsoft.EntityFrameworkCore.Relational.dll).
Поковыряться можно, конечно, если интересно.

Добавлено через 5 минут
По идее, разработчикам с опытом, это даже полезнее, чем чтение книжек по архитектуре, в какой-то степени.
1
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.08.2022, 12:52 3
Цитата Сообщение от kotelok Посмотреть сообщение
someContext.Database.OpenConnection();
Боже мой. Зачем?
0
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
10.08.2022, 19:07  [ТС] 4
Цитата Сообщение от Usaga Посмотреть сообщение
Зачем?
Для контролируемого открытия соединения и явного создания транзакции для него.
И чтобы потом это соединение можно было исползовать как в рамках EF-контекста, так и для прямых запросов к базе.
0
389 / 254 / 66
Регистрация: 12.04.2020
Сообщений: 1,329
10.08.2022, 21:51 5
Цитата Сообщение от kotelok Посмотреть сообщение
Для контролируемого открытия
а что есть неконтролируемое открытие базы?
0
HF
1163 / 749 / 181
Регистрация: 09.09.2011
Сообщений: 2,314
Записей в блоге: 2
10.08.2022, 22:21 6
Позанудстсвую с наводящими вопросами.

Цитата Сообщение от kotelok Посмотреть сообщение
Есть обычный EF-контекст (NET6).
C#
1
var someContext = new SomeContext(options);
Для которого далее выполняется:
C#
1
someContext.Database.OpenConnection();
Это точно обычный контекст? Где работал с EF, мы только передаём строку подключения в конструктор контекста и забываем об этом навсегда. А вот для ADO.NET подходов это обычное дело - самим строку считать, коннект открыть, коннект проверить, закрыть, почистить... Вот "под капотом" у EF и лежит ADO.
Или я ошибаюсь, или кто-то с EF работает как с ADO и получил неконтролируемое поведение, которое EF гарантирует (в общем в целом).

Цитата Сообщение от kotelok Посмотреть сообщение
На всякий случай вывел в логи хэш самого контекста, хэш коннекта 'someContext.Database.GetDbConnection().GetHashCode()'. Разные, т.е. это точно полностью новый экземпляр и контекста, и самого коннекта к базе.
Что есть GetHashCode? От чего он считался? Он может быть разный для разных экземпляров и разный для одних. Например, один коннект к БД имеет свой уникальный ИД. Почему бы какому-то клиенту не реализовать статический коннект для всех экземпляров? То о чём и сказал выше IamRain.

Цитата Сообщение от IamRain Посмотреть сообщение
Видимо, под капотом реализован circuit breaker.
А ещё есть такая штука - пул запросов. Тема тоже интересная.

Цитата Сообщение от kotelok Посмотреть сообщение
Для контролируемого открытия соединения и явного создания транзакции для него.
И чтобы потом это соединение можно было исползовать как в рамках EF-контекста, так и для прямых запросов к базе.
Вопрос - вы абсолютно точно понимаете что делаете? К тому что уверены что вам нужно именно контролировать открытые соединения и использовать как-то по особенному?
Почему-то я всегда считал, начитавшить документации и описаний, что открыв контекст, открыв транзакцию, или передав транзакцию в соединение, я гарантирую что всё будет выполняться в рамках этой транзакции или соединения. До момента когда я явно скажу "всё... готово.. записывай.. закрывай..."
У каждого действия должна быть причина. Если вы скажете что у вас запросы выполняются в хаотичных подключениях, вне транзакций... вот это да, проблема и стоит с этим и разбираться. А пока, это какие-то непонятные и пугающие действия.
Из-за такого понимания жизненного цикла, в коде начинают встречаться трай-кэчи которые выполняют несколько повторных действий ("чтоб наверняка"), с последующим "гашением" в пустом catch. И на выходе метода "радостный успешный результат".
1
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
11.08.2022, 04:04 7
Цитата Сообщение от kotelok Посмотреть сообщение
Для контролируемого открытия соединения и явного создания транзакции для него.
И чтобы потом это соединение можно было исползовать как в рамках EF-контекста, так и для прямых запросов к базе.
Какой контроль? Зачем? ADO.NET умеет в ambient transactions (TransactionScope) без всякого ручного контроля со стороны разработчика.

То, что вы тут описали - лишнее. Сами проверьте:

C#
1
2
3
4
5
6
7
using var tran = new TransactionScope();
 
// запрос через EF
 
// прямое обращение к ADO
 
tran.Complete();
Такая транзакция сразу же аттачится к подключению в момент открытия подключения. А поскольку EF работает через тот же ADO.NET, то этот механизм ОБЩИЙ для всех способов работы с СУБД.

На занимайтесь ерундой, всё до вас уже было реализовано.
1
HF
1163 / 749 / 181
Регистрация: 09.09.2011
Сообщений: 2,314
Записей в блоге: 2
11.08.2022, 08:50 8
Цитата Сообщение от Usaga Посмотреть сообщение
Какой контроль? Зачем? ADO.NET умеет в ambient transactions (TransactionScope) без всякого ручного контроля со стороны разработчика.
Мои слова выше в эмоциональном стиле.

Я ещё добавлю.
Какой контроль, kotelok, вам нужен? Хотите контроль - избавляйтесь от EF и работайте на кошерном ADO.NET. Люди писали, старались, сахарили, оборачивали чтобы не было явного контроля, а вы...
0
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
11.08.2022, 12:11  [ТС] 9
Цитата Сообщение от HF Посмотреть сообщение
Хотите контроль - избавляйтесь от EF и работайте на кошерном ADO.NET
Там и то, и то. Т.е. в рамках одной HTTP-сессии разные сервисы запрашивают один общий EF-контекст и далее могут использовать его:
1. Либо как EF-контекст.
2. Либо брать из него подключение к базе и работать с ним напрямую в рамках той же транзакции (через DbCommand или через Dapper).

И во втором случае, полученное из EF-контекста подключение нужно открыть явным образом.
0
2304 / 1667 / 326
Регистрация: 14.08.2018
Сообщений: 5,484
Записей в блоге: 4
11.08.2022, 12:31 10
Цитата Сообщение от kotelok Посмотреть сообщение
И во втором случае, полученное из EF-контекста подключение нужно открыть явным образом.
А кто мешает просто SqlConnection использовать для ADO? Зачем тащить из EF?
0
HF
1163 / 749 / 181
Регистрация: 09.09.2011
Сообщений: 2,314
Записей в блоге: 2
11.08.2022, 12:31 11
Цитата Сообщение от kotelok Посмотреть сообщение
И во втором случае, полученное из EF-контекста подключение нужно открыть явным образом.
Объясните. Пример дайте. Проблему покажите. Ошибки.
Я могу вызывать прямые запросы через EF. Но мне не нужно что-то дополнительно открывать. Он сам откроет, он - прокси.
0
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
11.08.2022, 12:36  [ТС] 12
Цитата Сообщение от HF Посмотреть сообщение
Из-за такого понимания жизненного цикла, в коде начинают встречаться трай-кэчи которые выполняют несколько повторных действий
Это тоже есть, но только для особых случаев и с последующей выдачей исключения (если не получилось).

Конкретная задача:
1. Пришёл HTTP-запрос.
2. Выполнился набор мидлваров до контроллера.
3. Далее контроллер должен вызвать сервис для обработки запроса. Сервис (через DI) может запросить другие сервисы. И все они должны работать в рамках одного подключения и одной транзакции, независимо от используемой библиотеки (EF, Dapper, ADO.NET).
4. Первый нюанс: подключения должны быть разными, в заивисимости от типа HTTP-запроса. Если GET-запрос, то используется аккаунт БД, имеющий права "только чтение" транзакция с уровнем изоляции 'read committed'. Если POST/PUT/DELETE/PATCH-запрос, то используется аккаунт БД с правами на запись и транзакция с изоляцией 'serializable'.
5. Второй нюанс: если вызов сервиса завершился с SQL-исключением 'transaction was deadlocked', то надо на уровне контроллера (ну или где-то между контроллером и сервисом) закрыть исходное подключение, создать новое и выполнить ещё одну попытку вызвать сервис. Если и со второй попытки не получилось, тогда уже вернуть ошибку. Все прочие ошибки обрабатываются штатным образом, т.е. после первой же попытки выдают в ответ либо HTTP-4XX, либо HTTP-500.

Добавлено через 1 минуту
Цитата Сообщение от HF Посмотреть сообщение
Я могу вызывать прямые запросы через EF
Создать EF-контекст.
НЕ выполнять через него никакие запросы.
Взять из него подключение.
Использовать это подключение для выполнения запроса ADO.NET (т.е. использовать, например, в SqlCommand).
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
11.08.2022, 12:44 13
Лучший ответ Сообщение было отмечено kotelok как решение

Решение

Цитата Сообщение от kotelok Посмотреть сообщение
одной транзакции
Это вопрос TransactionScope. Подключение одно шарить не нужно при этом! Их можно сколько угодно открывать и закрывать в рамках области видимости одной ambient транзакции.

Описанные вами сложности происходят из незнания этого механизма)
1
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
13.09.2022, 12:47  [ТС] 14
Цитата Сообщение от Usaga Посмотреть сообщение
Это вопрос TransactionScope
Не подскажете, как правильно его использовать?

Попробовал вот так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        var connectionString = "Server=localhost;Database=Some;Trusted_Connection=True;";
 
        using (var scope = new TransactionScope(
            TransactionScopeOption.RequiresNew,
            new TransactionOptions
            {
                IsolationLevel = System.Transactions.IsolationLevel.Serializable
            },
            TransactionScopeAsyncFlowOption.Enabled))
        {
            //EF context
            var builder = new DbContextOptionsBuilder<SomeContext>();
            builder.UseSqlServer(connectionString);
            var ctx = new SomeContext(builder.Options);
 
            //SQL Connection
            var conn = new SqlConnection(connectionString);
            conn.Open();
 
            //
            var products = ctx.Docs.ToList();
 
            //
            scope.Complete();
        };
Не работает, на 21-й строке выдаёт исключение:
System.PlatformNotSupportedException: 'This platform does not support distributed transactions.'
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
13.09.2022, 13:13 15
Цитата Сообщение от kotelok Посмотреть сообщение
Не работает, на 21-й строке выдаёт исключение:
Вы сделали дичь. На строке №18 вы открыли первое подключение, а в строке №21 открылось второе, в рамках одной транзакции. И получили распределённую транзакцию, а это или удар по производительности (в случае, если СУБД такое вообще поддерживает) или исключение, как в вашем случае.

Если собрались смешивать прямую работу с ADO и EF, то надо открывать и закрывать подключения в промежутках между обращениями к EF'у. Иначе получите, что получаете.

Ну и транзакцию вы тоже неправильно создали. Зачем TransactionScopeAsyncFlowOption.Enabled, если в рамках транзакции нет асинхронщины? Зачем System.Transactions.IsolationLevel.Serializable, если это уровень изоляции по умолчанию?

Добавлено через 1 минуту
А это что за дичь?

C#
1
2
var builder = new DbContextOptionsBuilder<SomeContext>();
builder.UseSqlServer(connectionString);
И почему контекст руками создаёте? Если уж создаёте руками, то строку подключения можно напрямую передавать. А билдер этот нужен для IoC, как обёртка над строкой подключения...
1
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
13.09.2022, 13:17  [ТС] 16
Цитата Сообщение от Usaga Посмотреть сообщение
Зачем TransactionScopeAsyncFlowOption.Enabled, если в рамках транзакции нет асинхронщины
В реальном проекте есть асинхронные вызовы.

Цитата Сообщение от Usaga Посмотреть сообщение
Зачем System.Transactions.IsolationLevel.Serializable, если это уровень изоляции по умолчанию?
Затем, чтобы каждый раз не додумывать, что там по умолчанию.

Цитата Сообщение от Usaga Посмотреть сообщение
Если собрались смешивать прямую работу с ADO и EF, то надо открывать и закрывать подключения в промежутках между обращениями к EF'у. Иначе получите, что получаете.
Как это вписать в DI? Запрашивать фабрику, затем у фабрики запрашивать IDbConnection и EF-контекст, потом не забывать их правильно закрывать. В т.ч. пред любыми вызовами вложенных сервисов, которые (вероятно) могут тоже работать с базой (а могут и не работать).

Какая-то сомнительная полезность 'TransactionScope' получается под этот сценарий.

Изначальный вариант с явным открытием подключения EF-контекста и с его последующим переиспользованием для ADO и Dapper выглядит намного менее проблемным для кода сервисов.

Добавлено через 1 минуту
Цитата Сообщение от Usaga Посмотреть сообщение
И почему контекст руками создаёте? Если уж создаёте руками, то строку подключения можно напрямую передавать
Потому что это пример для форума. Пример, на котором повторяется. Без необходимости словами описывать окружение или копипастить кучу кусочков кода с регистрацией, запросом и настройкой контекста (для полноты картины).
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
13.09.2022, 13:22 17
Цитата Сообщение от kotelok Посмотреть сообщение
В реальном проекте есть асинхронные вызовы.
Тады ок)

Цитата Сообщение от kotelok Посмотреть сообщение
Затем, чтобы каждый раз не додумывать, что там по умолчанию.
Не надо ничего додумывать. По умолчанию там только одно значение - Serializable.

Цитата Сообщение от kotelok Посмотреть сообщение
Как это вписать в DI? Запрашивать фабрику, затем у фабрики запрашивать IDbConnection и EF-контекст, потом не забывать их правильно закрывать. В т.ч. пред любыми вызовами вложенных сервисов, которые (вероятно) могут тоже работать с базой (а могут и не работать).
Какая-то сомнительная полезность 'TransactionScope' получается.
Изначальный вариант с явным открытие подключения EF-контекста и с его последующим переиспользованием для ADO и Dapper был намного менее проблемным для кода сервисов.
Вы у DI запрашиваете экземпляр контекста и всё. И ничего не диспозите. Для этого у вас есть scope сервисов. Если у вас некий API, то этот скоуп уже определён жизнью контроллера. Если какое-то обычное десктопное приложение, то уже сами эти скоупы определяете. В том числе и во вложенных методах. Фразу про TransactionScope я не понял. Откуда вывод про сомнительную полезность?

C#
1
2
3
4
5
6
7
8
9
{
using scope = diContaner.CreateScope()
 
var ctx = scope.GetService<Context>();
 
...
 
}
// тут контекста уже нет
0
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
13.09.2022, 13:34  [ТС] 18
Цитата Сообщение от Usaga Посмотреть сообщение
Вы у DI запрашиваете экземпляр контекста и всё. И ничего не диспозите.
Сценарий первый - сервису нужен и EF-контекст, и IDbConnection. Он их оба запрашивает и оба использует в какой-то последовательности. При этом внутри самого сервиса не хочется явно открывать/закрывать подключения и транзакции.

Сценарий второй:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public sealed class ServiceOne
{
    private readonly SomeContext _ctx;
    private readonly ServiceTwo _serviceTwo;
 
    public ServiceOne(SomeContext ctx, ServiceTwo serviceTwo)
    {
        _ctx = ctx;
        _serviceTwo = serviceTwo;
    }
 
    public void DoSome(SomeData data)
    {
        //Разный CRUD с контекстом.
        var docs = _ctx.Docs.ToList();
 
        //Вызов другого сервиса.
        _serviceTwo.DoOther();
 
        //Ещё какой-то CRUD с контекстом.
        _ctx.Docs.Add(....);
        _ctx.SaveChanges();
    }
}
 
public sealed class ServiceTwo
{
    private readonly IDbConnection _conn;
 
    public ServiceTwo(IDbConnection conn)
    {
        _conn = conn;
    }
 
    public void DoOther()
    {
        using (var cmd = <команда с _conn>)
        {
            //CRUD-операции с _conn.
        }
    }
}
0
1215 / 806 / 244
Регистрация: 08.08.2014
Сообщений: 2,371
14.09.2022, 08:51  [ТС] 19
Цитата Сообщение от Usaga Посмотреть сообщение
Фразу про TransactionScope я не понял. Откуда вывод про сомнительную полезность?
Я же уточнил - под этот сценарий. Т.е. когда в рамках одной транзакции с одной базой работает несколько сервисов разными способами и в разном порядке - один через EF, другой через IDbConnection(+ иногда Dapper), третий и то, и то использует.
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
14.09.2022, 16:31 20
Цитата Сообщение от kotelok Посмотреть сообщение
Сценарий первый - сервису нужен и EF-контекст, и IDbConnection. Он их оба запрашивает и оба использует в какой-то последовательности. При этом внутри самого сервиса не хочется явно открывать/закрывать подключения и транзакции.
Открывать\закрывать подключение придётся. Более того: это будет даже весьма просто. Ведь вся работа с базой будет изолирована в другом сервисе - репозитории. А он будет управлять подключением. Это правильный подход.

Цитата Сообщение от kotelok Посмотреть сообщение
Сценарий второй:
Тут я проблемы не вижу. Каждый метод второго сервиса открывает подключение явно, а потом его закрывает. Никаких пересечений с EF. Одна транзакция на оба способа работы с СУБД.

Цитата Сообщение от kotelok Посмотреть сообщение
Я же уточнил - под этот сценарий. Т.е. когда в рамках одной транзакции с одной базой работает несколько сервисов разными способами и в разном порядке - один через EF, другой через IDbConnection(+ иногда Dapper), третий и то, и то использует.
Так, а сомнительность TransactionScope откуда берётся? Основное требование - не держать подключение к базе постоянно открытым и всё. Выполняется оно очень легко и просто.
1
14.09.2022, 16:31
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
14.09.2022, 16:31
Помогаю со студенческими работами здесь

Раздача вайфая в windows server (при повторном подключении не удается соединиться)
Всем доброго времени суток! Суть проблемы: имеется ноут выполняющий роль файлопомойки и роутера,...

Перестает "работать" сервер при повторном подключении клиента
Здравствуйте! Я совершенно недавно начал изучение С++ по этому столкнулся с проблемой которую не...

Странности в подключении
Всем привет, прошу помочь, так как сам уже не понимаю что делать. У нашей компании есть сервер....

Странности в сетевом подключении
Привет всем от Новечка! Имею один комутатор на 8 разьемов, подключил все к коробочкам, обжал...

Сделать так чтобы при подключении нулевого порта выполнялся один цикл кода а при подключении другого - другой
Как сделать так чтобы при подключении нулевого порта выполнялся один цикл кода, а при подключении...

При первом нажатии кнопки - выполнялся один код,при повторном другой и тд
Имеется код : document.onkeydown = function(z) { if (z.keyCode==&quot;120&quot;) { var h =...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru