Форум программистов, компьютерный форум, киберфорум
C#: Базы данных
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
1 / 1 / 0
Регистрация: 02.06.2024
Сообщений: 18
.NET 5

Один ко многим. Как правильно сохранить дочернюю сущность?

05.07.2024, 18:51. Показов 774. Ответов 5

Студворк — интернет-сервис помощи студентам
Один ко многим. Как правильно сохранить дочернюю сущность?
Логика кратко:
- список сделок;
- к каждой сделке прикрепляется список картинок.

БД -SQLite.
Проект: https://github.com/Dev65432/WpfApp1

Вопрос: Как правильно сохранить дочернюю сущность?


Пробую сделать так:
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
43
private async Task InsertPictureOfOpeningOftransactionExecuted()
{
    try
    {
        if (Clipboard.ContainsImage())
        {
            string typeDeal = "open";
            // Имя файла
            string nameFile = $"idDeal-{SelectedDeal.Id}_type-{typeDeal}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.jpg";
 
            // Сохранить в файл
            SaveToFile(nameFile);
 
            // Сохранить в БД
            var rnd = new Random();
 
            int i = rnd.Next(0, 10000);
 
            Picture picture = new Picture
            {
                Id = i,
                Name = nameFile,                        
                DealId = SelectedDeal.Id,
                Deal = SelectedDeal
            };
 
                SelectedDeal.Pictures.Add(picture);
            /// _DealsRepository.Update(SelectedDeal);
            _pictureRepository.Update(picture);
        }
        else
        {
            // Console.WriteLine("Буфер обмена не содержит изображения.");
        }
 
    }
    catch (Exception ex)
    {
        string msg = ex.Message;
        throw;
    }
 
}

в `_pictureRepository.Update(picture);` получаю ошибку.

Текст ошибки:

Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded.
See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

Перевод ----------
Ожидалось, что работа базы данных затронет 1 строку(ы), но на самом деле это повлияло на 0 строк(ов). Возможно, данные были изменены или удалены с момента загрузки объектов. Смотрите http://go.microsoft.com/fwlink/?LinkId=527962 для получения информации о понимании и обработке исключений оптимистичного параллелизма.


В интернетах пишут, что нужно
- взять сущность из БД;
- обновить свойства;
- обновить в БД.

Пробую так - см. код ниже
Не получается.


C#
1
2
3
4
5
6
7
8
9
10
11
// _dealsRepository --- --- --- --- ---
var dealUp = _dealsRepository.Get(SelectedDeal.Id);                    
dealUp.Pictures = SelectedDeal.Pictures;
_dealsRepository.Update(dealUp);
 
// Или 
 
// _pictureRepository --- --- --- --- ---
var picUp = _pictureRepository.Get(picture.Id);
picUp = picture;
_pictureRepository.Update(picUp);

Entity
Кликните здесь для просмотра всего текста
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
public abstract class Entity : IEntity, INotifyPropertyChanged
{
        // public int Id { get; set; }
 
    private int _id;
 
    public int Id
    {
        get { return _id; }
        set 
        { 
            _id = value;
            OnPropertyChanged(nameof(Id));
        }
    }
 
 
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
 
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
 
        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}


NamedEntity
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class NamedEntity : Entity
{
    
    // public string Name { get; set; }
 
    private string _name;
    
    [Required]
    public string Name
    {
        get { return _name; }
        set 
        { 
            _name = value;
            OnPropertyChanged(nameof(Name));   
        }
    }
}


Deal
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
public class Deal : NamedEntity 
{
    // Свойства Id, Name находятся в базоввом классе.
 
    public ICollection<Picture> Pictures { get; set; }
 
    public Deal()
    {
        Pictures = new List<Picture>();
    }
}



Picture
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
public class Picture : NamedEntity
{
    // Свойства Id, Name находятся в базоввом классе.
 
    public int? DealId { get; set; }
    
    public Deal Deal { get; set; }
}



DbRepository<T>
Кликните здесь для просмотра всего текста
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
internal class DbRepository<T> : IRepository<T> where T : Entity, new()
{
    private readonly cntxDBWpfApp1 _db;
    private readonly DbSet<T> _Set;
 
    public bool AutoSaveChanges { get; set; } = true;
 
    public DbRepository(cntxDBWpfApp1 db)
    {
        _db = db;
        _Set = db.Set<T>();
    }
 
    public virtual IQueryable<T> Items => _Set;
 
    public T Get(int id) => Items.SingleOrDefault(item => item.Id == id);
 
    public async Task<T> GetAsync(int id, CancellationToken Cancel = default) => await Items
        .SingleOrDefaultAsync(item => item.Id == id, Cancel)
        .ConfigureAwait(false);
 
    public T Add(T item)
    {
        if (item is null) throw new ArgumentNullException(nameof(item));
        _db.Entry(item).State = EntityState.Added;
        if (AutoSaveChanges)
            _db.SaveChanges();
        return item;
    }
 
    public async Task<T> AddAsync(T item, CancellationToken Cancel = default)
    {
        if (item is null) throw new ArgumentNullException(nameof(item));
        _db.Entry(item).State = EntityState.Added;
        if (AutoSaveChanges)
            await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
        return item;
    }
 
    public void Update(T item)
    {
        if (item is null) throw new ArgumentNullException(nameof(item));
        // _db.Set<T>().Attach(item);
        _db.Entry(item).State = EntityState.Modified;
        if (AutoSaveChanges)
            _db.SaveChanges();
    }
 
    public async Task UpdateAsync(T item, CancellationToken Cancel = default)
    {
        if (item is null) throw new ArgumentNullException(nameof(item));
        _db.Entry(item).State = EntityState.Modified;
        if (AutoSaveChanges)
            await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
    }
 
    public void Remove(int id)
    {
        //var item = Get(id);
        //if (item is null) return;
        //_db.Entry(item);
 
        var item = _Set.Local.FirstOrDefault(i => i.Id == id) ?? new T { Id = id };
 
        _db.Remove(item);
 
        if (AutoSaveChanges)
            _db.SaveChanges();
    }
 
    public async Task RemoveAsync(int id, CancellationToken Cancel = default)
    {
        _db.Remove(new T { Id = id });
        if (AutoSaveChanges)
            await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
    }
}



DealsRepository
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
class DealsRepository : DbRepository<Deal>
{
    public override IQueryable<Deal> Items => base.Items;
 
    public DealsRepository(cntxDBWpfApp1 db) : base(db) { }
}



PicturesRepository
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
class PicturesRepository : DbRepository<Picture>
{
    public override IQueryable<Picture> Items => base.Items;
 
    public PicturesRepository(cntxDBWpfApp1 db) : base(db) { }
}


Deals2ViewModel
Кликните здесь для просмотра всего текста
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
public class Deals2ViewModel : ViewModelBase
{
    IConfiguration _configuration;
    private IRepository<Deal> _dealsRepository;        
    private IRepository<Picture> _pictureRepository;
 
    public Deals2ViewModel(IConfiguration configuration,
                            IRepository<Deal> DealsRepository,                               
                            IRepository<Picture> pictureRepository)
    {
        _configuration = configuration;
        _dealsRepository = DealsRepository;            
        _pictureRepository = pictureRepository;            
    }
 
    private string _title = "Заголовок. DealsViewModel ";
 
    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;
            OnPropertyChanged(nameof(Title));
        }
    }
 
    #region Deals : BindingList<Deal> - Коллекция сделки
    private Deal _selectedDeal;
 
    public Deal SelectedDeal
    {
        get { return _selectedDeal; }
        set
        {
            _selectedDeal = value;
            OnPropertyChanged(nameof(SelectedDeal));
        }
    }
 
    private BindingList<Deal> _deals;
 
    public BindingList<Deal> Deals
    {
        get { return _deals; }
        set
        {
            _deals = value;
            OnPropertyChanged(nameof(Deals));
        }
    }
    #endregion
 
    #region  Command LoadDataCommand - Команда загрузки данных из репозитория
    /// <summary>Отобразить представление статистики</summary>
    private ICommand _loadDataCommand;
 
    public ICommand LoadDataCommand
    {
        get
        {
            return _loadDataCommand ??
                (_loadDataCommand = new RelayCommand(() => OnLoadDataExecuted()));
        }
    }
 
    private async Task OnLoadDataExecuted()
    {
        try
        {
            
            // Deals 
            var items = await _dealsRepository.Items.ToArrayAsync();
            Deals = new BindingList<Deal>(items);
            SelectedDeal = Deals[1];
            
 
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
            throw;
        }
 
    }
 
    #endregion
 
 
    #region  Command InsertPictureOfOpeningOftransactionCommand - Команда Вставить картинку открытия сделки
    /// <summary>Отобразить представление статистики</summary>
    private ICommand _insertPictureOfOpeningOftransactionCommand;
 
    public ICommand InsertPictureOfOpeningOftransactionCommand
    {
        get
        {
            return _insertPictureOfOpeningOftransactionCommand ??
                (_insertPictureOfOpeningOftransactionCommand
                        = new RelayCommand(()
                        => InsertPictureOfOpeningOftransactionExecuted()));
        }
    }
 
    private async Task InsertPictureOfOpeningOftransactionExecuted()
    {
        try
        {
            if (Clipboard.ContainsImage())
            {
                string typeDeal = "open";
                // Имя файла
                string nameFile = $"idDeal-{SelectedDeal.Id}_type-{typeDeal}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.jpg";
 
                // Сохранить в файл
                SaveToFile(nameFile);
 
                // Сохранить в БД
                var rnd = new Random();
 
                int i = rnd.Next(0, 10000);
                
                Picture picture = new Picture
                {
                    Id = i,
                    Name = nameFile,                        
                    DealId = SelectedDeal.Id,
                    Deal = SelectedDeal
                };
 
                SelectedDeal.Pictures.Add(picture);
 
                // _dealsRepository --- --- --- --- ---
                // var dealUp = _dealsRepository.Get(SelectedDeal.Id);                    
                // dealUp.Pictures = SelectedDeal.Pictures;
 
                //_dealsRepository.Update(dealUp);
 
                // _pictureRepository --- --- --- --- ---
                    var picUp = _pictureRepository.Get(picture.Id);
                        picUp = picture;
                    _pictureRepository.Update(picUp);
            }
            else
            {
                // Console.WriteLine("Буфер обмена не содержит изображения.");
            }
 
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
            throw;
        }
 
    }
 
    #endregion
 
 
    #region MethodsPrivate
    private void SaveToFile(string nameFile)
    {
       // Код
    }
    #endregion
 
}
Миниатюры
Один ко многим. Как правильно сохранить дочернюю сущность?  
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
05.07.2024, 18:51
Ответы с готовыми решениями:

Как правильно создать отношение один ко многим в entity framework
Здравствуйте. Подскажите пожалуйста как правильно создать отношение 1 ко многим. При этом на стороне &quot;ко многим&quot; еще есть...

Sqlite3, связь один ко многим, как ускорить выборку по ключу или правильно написать select. join
вот так создается таблица: sqlite3_exec(db, &quot;CREATE TABLE IF NOT EXISTS lib (id INT PRIMARY KEY NOT NULL, lid INT, name CHAR, path...

Правильно ли связал таблицы (Один к многим)?
Есть две таблицы: create table Conferences( ConferenceID int not null , ConferenceName nvarchar(10) not null, primary key...

5
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
06.07.2024, 11:50
Цитата Сообщение от cyber7778 Посмотреть сообщение
Как правильно сохранить дочернюю сущность?
Вы создаете новое изображение, присваиваете его первичному ключу случайное значение и вызываете метод Обновить, который генерирует запрос вида UPDATE ... SET ... WHERE Id = [ваше случайное значение]
Естественно, если в базе еще нет изображения с таким ключом, то обновлять нечего — отсюда и ошибка, т.е. надо вызывать либо Update, либо Add в зависимости от того, новая это запись или нет.
Если бы изображение с тами ключом уже было в базе, то вы бы поменяли картинку для какой-то другой сделки, т.е. вам надо так же определиться кто отвечает за присваивание первичного ключа новым записям: вы или база.
Если вы, то имеет смысл убедиться, что присваиваемое значение новой сущности еще не существует в базе.
1
1 / 1 / 0
Регистрация: 02.06.2024
Сообщений: 18
06.07.2024, 16:23  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Вы создаете новое изображение, присваиваете его первичному ключу случайное значение и вызываете метод Обновить, который генерирует запрос вида
UPDATE ... SET ... WHERE Id = [ваше случайное значение]
Виноват... Признаю..

Суть вопроса наверное в том как добавить дочернюю сущность в таблицу:
- через обновление родительской, используя "_dealsRepository"? - если это возможно.
- через непосредственное добавление, используя "_pictureRepository" - это работает.

Пример
Ситуация: в таблице Pictures сущности Picture с сгенерированным Id нет.

Я рассчитываю на такую логику:
- создал Picture в коде (проверил, в таблице Pictures сущности Picture с сгенерированным id нет);
- добавил новый Picture в список картинок(Pictures) сделки (Deal). Т.е. SelectedDeal.Pictures.Add(picture);
- Результат: предыдущей операцией я как бы получается выполнил обновление `SelectedDeal`. И выполняю _dealsRepository.Update(SelectedDeal);
- А умный EF дальше сам разберётся что ему делать с Picture: Add в Pictures или Updtae в Pictures.

Мои ожидания(цель): не нужно пихать в конструктор Deals2ViewModel другие репозитории кроме, как "_dealsRepository"

Или это так не работает?

Сейчас.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Deals2ViewModel : ViewModelBase
{    
    private IRepository<Deal> _dealsRepository;        
    private IRepository<Picture> _pictureRepository;
 
    public Deals2ViewModel(IConfiguration configuration,
                            IRepository<Deal> DealsRepository,                               
                            IRepository<Picture> pictureRepository)
    {
        _configuration = configuration;
        _dealsRepository = DealsRepository;            
        _pictureRepository = pictureRepository;            
    }
Миниатюры
Один ко многим. Как правильно сохранить дочернюю сущность?  
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
06.07.2024, 17:01
Лучший ответ Сообщение было отмечено cyber7778 как решение

Решение

Цитата Сообщение от cyber7778 Посмотреть сообщение
- через обновление родительской, используя "_dealsRepository"? - если это возможно.
Возможно, но тогда придется либо отслеживать весь граф объектов в контексте, т.е. обновлять через тот же контекст, через который сущность была получена изначально; либо вручную указывать какие дочерние объекты обновились/добавились.

Цитата Сообщение от cyber7778 Посмотреть сообщение
- через непосредственное добавление, используя "_pictureRepository" - это работает.
Самый простой вариант, ага.

Цитата Сообщение от cyber7778 Посмотреть сообщение
- создал Picture в коде (проверил, в таблице Pictures сущности Picture с сгенерированным id нет);
Если Id числовой, то почему бы не поручить создание Id самой базе? В будущем будет меньше головной боли если появится необходимость в одновременном доступе из разных копий приложения.

Цитата Сообщение от cyber7778 Посмотреть сообщение
- добавил новый Picture в список картинок(Pictures) сделки (Deal). Т.е. SelectedDeal.Pictures.Add(picture);
- Результат: предыдущей операцией я как бы получается выполнил обновление `SelectedDeal`. И выполняю _dealsRepository.Update(SelectedDeal);
- А умный EF дальше сам разберётся что ему делать с Picture: Add в Pictures или Updtae в Pictures.
Это возможно, если весь процесс выполняется с использованием одного и того же контекста, если включено отслеживание изменений и если используются прокси-типы для генерации моделей.
Если приложение стучится в базу напрямую, то в принципе это возможно. Если же с базой работает веб-сервис, к которому уже подключаются клиенты, то этот вариант можно исключить.

Цитата Сообщение от cyber7778 Посмотреть сообщение
Мои ожидания(цель): не нужно пихать в конструктор Deals2ViewModel другие репозитории кроме, как "_dealsRepository"
Этот паттерн в DDD называется Aggregate Root. В принципе, вменяемый подход, но не всегда просто реализуем, особенно если граф связей большой.

Есть еще другой способ: использовать метод TrackGraph, позволяющий подключать к контексту весь граф объектов и вручную определять состояние каждого. Хорошо подходит при использовании Aggregate Root в серверной части.

В общем, варианты от наиболее простого к наиболее сложному:
1. Использовать отдельные репозитории для каждой сущности
2. Использовать один контекст на протяжении всей сессии и использовать поставляемое в EF остлеживание изменений.
3. Использовать TrackGraph и вручную метить каждый объект как добавленный/измененный/удаленный.
1
1 / 1 / 0
Регистрация: 02.06.2024
Сообщений: 18
06.07.2024, 17:16  [ТС]
kolorotur,
Какой тип данных лучше(практикуется) использовать для id в SQLite?
Т.е.
- id в SQLite - типа данных?
- id в модели Picture - типа данных?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
06.07.2024, 18:02
Лучший ответ Сообщение было отмечено cyber7778 как решение

Решение

Цитата Сообщение от cyber7778 Посмотреть сообщение
Какой тип данных лучше(практикуется) использовать для id в SQLite?
Конкретно с SQLite не сильно много работал, потому нюансы не знаю, но в целом ситуация примерно такая:
1. Традиционно используется числовой тип с автоинкрементом, который присваивается при добавлении записи в таблицу — за все отвечает база и гарантирует*, что дубликатов не будет.
2. Id присваивает клиент, но используется тип, гарантирующий уникальность: UUID или ULID. Лучше подходит для распределенных приложений и для СУБД, умеющих эффективно работать с этими типами (PostgreSQL, например).

В принципе, в вашем случае наверное подойдет простой автоувеличивающийся счетчик, т.е. первый вариант.
Бонусом EF трактует значение 0 как добавление нового элемента и сама обновит его до присвоенного базой значения после сохранения.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
06.07.2024, 18:02
Помогаю со студенческими работами здесь

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

Связь "один ко многим". Как правильно создать таблицы?
Доброго всем дня! Например у меня есть таблица `users` и `district`. Как лучше реализовать таблицы эти если пользователю могут...

Как правильно реализовать отношение "один ко многим". Пример - в первом посте
такой пример. имеются две таблицы: в одной - сотрудники, в другой - объекты. сотрудники могут иметь доступ к объектам. как структурно...

Hibernate как сохранить сущность
Здраствуйте. Есть задание. Создать базу данных «Банк» с таблицами «Пользователи», «Транзакции», «Счета» и «Курсы валют». Счет бывает 3-х...

Обозначьте связи между таблицами БД и тип связи (один-ко-многим, один-к-одному, многие-ко-многим).
обозначьте связи между таблицами БД и тип связи (один-ко-многим, один-к-одному, многие-ко-многим). Обозначьте типы данных центральной...


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

Или воспользуйтесь поиском по форуму:
6
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru