Deutsch

Непонятки с EF

1778  1 2 3 4 все
AlexNek патриот25.12.23 20:30
AlexNek
NEW 25.12.23 20:30 

Играюсь я тут дома с прогой. НЕТ 8.0

Простой поиск

_dbContext.Users.FirstOrDefault(u => u.Email == email);


Выдаёт непонятную ошибку.

"

Microsoft.Data.SqlClient.SqlException

Invalid column name 'UserId'.

at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)

at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)

...

at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)

at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()

at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()

...

"

При этом юнит тесты с репозиторием и реальной базой работают без проблем. (И с тем же db Context классом)

А вот интеграционный тест выдает уже ошибку, правда сделал на уровень выше с DI и инициализацией базы как в приложении.


Какие будут идеи?

#1 
AlexNek патриот25.12.23 21:15
AlexNek
25.12.23 21:15 
в ответ AlexNek 25.12.23 20:30

Ошибка исчезает при переименование колонки UserId->Id. UserId это и тип колонки

Ошибка в НЕТ ЕФ?

#2 
Срыв покровов патриот25.12.23 21:26
NEW 25.12.23 21:26 
в ответ AlexNek 25.12.23 21:15

как называется колонка в БД?

Есть ли для класса User класс конфигурации для EF или конфигурация сделана в виде [Column("Id")]?


#3 
AlexNek патриот25.12.23 21:35
AlexNek
NEW 25.12.23 21:35 
в ответ Срыв покровов 25.12.23 21:26

База генерится так

public sealed class UserConfigurations : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        ConfigureUsersTable(builder);
    }
    private void ConfigureUsersTable(EntityTypeBuilder<User> builder)
    {
        builder
            .ToTable("Users");
        builder
            .HasKey(u => u.Id);
        builder
            .Property(u => u.Id)
            //.HasColumnName("UserId")
            .ValueGeneratedNever();
        //.HasConversion(
        //    id => id.Value,
        //    value => UserId.Create(value));
...

С этой строкой не работает .HasColumnName("UserId")

Конверсия сделана глобальной - internal class UserIdConverter : ValueConverter

public sealed class User : AggregateRoot<UserId, Guid>
{
...
    private User(
        UserId id,
#4 
AlexNek патриот25.12.23 21:57
AlexNek
NEW 25.12.23 21:57 
в ответ AlexNek 25.12.23 21:35

Похоже совсем другое. Как заработало, начал данные искать и тут фигвам, они оказались в другой базе. Где как раз имя колонки Id шок

#5 
alex445 коренной житель26.12.23 01:37
NEW 26.12.23 01:37 
в ответ AlexNek 25.12.23 21:57

Т.е. дело оказалось в строке подключения, где указана другая база?

#6 
AlexNek патриот26.12.23 10:53
AlexNek
NEW 26.12.23 10:53 
в ответ alex445 26.12.23 01:37
оказалось в строке подключения, где указана другая база?

в принципе да, но не всё так просто было.

Для запуска в проекте миграции нужен IDesignTimeDbContextFactory со своей строкой подключения в проекте "с базой"

А для запуска приложения используется другая строка подключения, обе строки должны быть идентичны. /Как их брать их одного места пока не придумал смущ/

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

#7 
MrSanders коренной житель26.12.23 15:08
NEW 26.12.23 15:08 
в ответ AlexNek 26.12.23 10:53
и юнит тесты дергали ее /для тестов тоже своя строка подключения/,

Глаз зацепился. Как только тест начинает лезть в БД это уже не юнит тест, а интеграционный.

#8 
AlexNek патриот26.12.23 15:31
AlexNek
NEW 26.12.23 15:31 
в ответ MrSanders 26.12.23 15:08
Как только тест начинает лезть в БД это уже не юнит тест, а интеграционный

тут несколько пунктов:

1. Мокать базу смысла не вижу. Обычно база в памяти, не фонтан, но некоторые ошибки можно найти

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

3. Интеграционный тест тоже есть, но он не на уровне репозитория, а "выше"


Хотя сейчас больше интересует другой вопрос как отмапить ссылки на агрегаты по ИД, если ИД - value object



#9 
MrSanders коренной житель26.12.23 17:25
NEW 26.12.23 17:25 
в ответ AlexNek 26.12.23 15:31

Я не говорю, что такие тесты не надо делать. Просто уточнил терминологию. Дверь надо называть дверью. Тест, работающий с базой (даже если в памяти) - не юнит тест, а интеграционный.

#10 
AlexNek патриот26.12.23 17:55
AlexNek
NEW 26.12.23 17:55 
в ответ MrSanders 26.12.23 17:25
Дверь надо называть дверью

По теории вполне возможно. Но если на практике она долгое время называлась дыркой и выглядит как дырка, то никто не захочет называть это дверью смущ Разве в проспекте можно написать - дверь в загадочное подземелье...

Если Moq есть, то нефиг называть энто интеграционным тестом.


ну и напрямую ничего про базы нет, кроме side effect.

"Unit tests focus on a single part of a whole application in total isolation, usually, a single class or function. Ideally, the tested component is free of side effects so it is as easy to isolate and test as possible."

"We have learned that, in practice, the isolation property of unit tests may not be enough for some functions. In this case, one solution is to test how parts of the application work together as a whole. This approach is called integration testing.

Unlike unit testing, integration testing considers side effects from the beginning. These side effects may even be desirable."

#11 
MrSanders коренной житель26.12.23 18:25
NEW 26.12.23 18:25 
в ответ AlexNek 26.12.23 17:55
ну и напрямую ничего про базы нет, кроме side effect.

Мна... Это как? Надо чтобы где-то было написано "юнит тесты не должны использовать базу данных"? И тогда ты поверишь что они действительно это не должны делать?


Не надо искать что-то в "side effects". "...in total isolation, usually, a single class or function".


Если Moq есть, то нефиг называть энто интеграционным тестом.

Неправильно. Во-первых: Mock. Во-вторых: представь, что у тебя три системы (сервиса) А, Б, В. Код из А использует Б и В. Сначала Б, потом В. И ты хочешь проверить интеграцию А с В. Конечно же, ты можешь (и даже должен) замОчить вызов Б. От этого тест не перестал быть интеграционным и стал намного более информативным. Если он сломался - не надо гадать, что у тебя накрылось, Б или В. Ты сразу знаешь что накрылось В.

#12 
AlexNek патриот26.12.23 20:21
AlexNek
NEW 26.12.23 20:21 
в ответ MrSanders 26.12.23 18:25
in total isolation

ладно пойдем от этого... Вот например функция и еще много подобных и посложнее. _dbContext другой класс, значит его нужно чем то заменить. Чем и как?

и как будем после тестировать сам _dbContext? И какой будет толк во всём этом разделении? Не чисто теоретически сервис А и сервис Б, а совершенно практически.

И что изменится если вместо одного названия MyRepo.UnitTest будет другое MyRepo.IntegrationTest? При этом все остальные IntegrationTest используют части "живой системы".

public async Task<Group?> GetMyGroupAsync(UserId ownerId, CancellationToken cancellationToken)
{
    Group? group = await _dbContext.Groups
                       .Include(a => a.Owner)
                       .Include(a => a.Users)
                       .Include(a => a.Information)
                       .FirstOrDefaultAsync(x => x.Owner.Id == ownerId, cancellationToken);
    return group;
}
#13 
MrSanders коренной житель26.12.23 20:40
NEW 26.12.23 20:40 
в ответ AlexNek 26.12.23 20:21, Последний раз изменено 26.12.23 20:41 (MrSanders)

Это очень хороший метод. Для юнит-тестов. Потому что для него юнит-тесты писать не надо. Опа. Он просто "обёртка" вокруг вызова внешней системы. Его надо тестировать интеграционным тестом. Который проверит, что с определённой тестовой БД, в которой есть (например) 10 записей, при поиске с UserId = 123 мы найдём ровно том, что ожидали.


А вот когда мы будем писать юнит-тесты для кода, который вызывает этот метод, мы будем мочить его вызовы. Сможем тестировать код, вызывающий наш гет без БД.

#14 
AlexNek патриот26.12.23 21:55
AlexNek
NEW 26.12.23 21:55 
в ответ MrSanders 26.12.23 20:40
Потому что для него юнит-тесты писать не надо.

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

Что меняется от использования другого названия? Тестироваться то будет ОДИН класс, который также написан нами. То есть формально будет юнит тест. Или нужно делать упор на "внешнюю систему", но ведь ее мы не должны тестировать, по умолчанию она работает "правильно".


что с определённой тестовой БД

Этот подход можно пользовать если мы хотим тестировать исключительно Query, а вот с Command будет посложнее, пусть даже база будет в локальном докере.


при поиске с UserId = 123 мы найдём ровно то, что ожидали

Это самое малое что меня будет интересовать в данном случае. А вот загрузится ли полностью владелец или пользователи будет гораздо интереснее.

А если там еще и джойны есть, то есть что проверять.

Статическая база никак не подходит.


А вот когда мы будем писать юнит-тесты для кода, который вызывает этот метод, мы будем мочить его вызовы.

С этим никаких вопросов нет, именно так и делается

#15 
Срыв покровов патриот26.12.23 22:36
NEW 26.12.23 22:36 
в ответ AlexNek 26.12.23 20:21

нп

А есть какие-то преимущества/недостатки хранения экземпляра dbContext в свойстве класса?

Первое, что приходит на ум, что вот такие вещи будут действовать во всех потоках.

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;

#16 
AlexNek патриот26.12.23 22:48
AlexNek
NEW 26.12.23 22:48 
в ответ Срыв покровов 26.12.23 22:36
А есть какие-то преимущества/недостатки хранения экземпляра dbContext в свойстве класса?

Что то не припоминаю подобного вопроса. смущ А что предлагаете вместо этого?

public class MyRepository
{
    private readonly MyDbContext _dbContext;
    public MyRepository(MyDbContext dbContext)
    {
        _dbContext = dbContext;
    }
#17 
alex445 коренной житель26.12.23 23:13
NEW 26.12.23 23:13 
в ответ AlexNek 26.12.23 22:48
_dbContext

Подчёркивания - давно дурной тон. Как и всякие префиксы b перед булевыми переменными, e перед перечислениями и т.п. IDE давно всё как надо подсвечивает и выдаёт мгновенные всплывающие подсказки по типам переменных и их принадлежности. Зачем захламлять код закорючками, которые помогали, когда IDE были лишь блокнотами с прикрученными компиляторами?

#18 
AlexNek патриот26.12.23 23:33
AlexNek
NEW 26.12.23 23:33 
в ответ alex445 26.12.23 23:13
Подчёркивания - давно дурной тон.

А вы вот яйца с какого конца разбиваете? ... Неправильно смущ

"this." безусловно лучше, особенно когда смотрю сразу целый экран.

#19 
Срыв покровов патриот26.12.23 23:57
NEW 26.12.23 23:57 
в ответ AlexNek 26.12.23 22:48

альтернатива это

void DeleteUserById(int id)

{
using (var context = new MyDbContext())

context.Users.Remove(x => x.Id == id);

context.SaveChanges();

}
}

В чем преимущества вашего метода?

#20 
1 2 3 4 все