< Summary

Information
Class: OzonRepositories.Data.ShipmentRepository
Assembly: OzonRepositories
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonRepositories/Data/ShipmentRepository.cs
Line coverage
45%
Covered lines: 38
Uncovered lines: 46
Coverable lines: 84
Total lines: 173
Line coverage: 45.2%
Branch coverage
47%
Covered branches: 16
Total branches: 34
Branch coverage: 47%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetOrCreateAsync()50%1903043.75%
GetAsync()100%11100%
GetByNumberAsync()50%2283.33%
GetAllAsync()100%210%
AddAsync()100%210%
DeleteAsync()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonRepositories/Data/ShipmentRepository.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using OzonDomains.Models;
 3using OzonRepositories.Context;
 4
 5namespace OzonRepositories.Data
 6{
 7    public class ShipmentRepository : MainRepository
 8    {
 139        public ShipmentRepository(ApplicationDbContext orderContext) : base(orderContext)
 10        {
 1311        }
 12
 13        /// <summary>
 14        /// Получает существующее отправление по номеру или создает новое.
 15        /// Возвращает Id отправления.
 16        /// </summary>
 17        /// <param name="shipmentNumber">Номер отправления</param>
 18        /// <returns>Id отправления</returns>
 19        public async Task<int> GetOrCreateAsync(string shipmentNumber)
 20        {
 1721            if (string.IsNullOrWhiteSpace(shipmentNumber))
 322                throw new ArgumentNullException(nameof(shipmentNumber));
 23
 1424            shipmentNumber = shipmentNumber.Trim();
 25
 1426            var local = _context.Shipments.Local
 1627                .FirstOrDefault(s => s.ShipmentNumber == shipmentNumber);
 1428            if (local != null && local.Id > 0)
 229                return local.Id;
 30
 1231            var existingId = await _context.Shipments
 1232                .AsNoTracking()
 1233                .Where(s => s.ShipmentNumber == shipmentNumber)
 1234                .Select(s => (int?)s.Id)
 1235                .FirstOrDefaultAsync();
 36
 1237            if (existingId.HasValue && existingId.Value > 0)
 738                return existingId.Value;
 39
 540            if (_context.Database.IsRelational())
 41            {
 342                var provider = _context.Database.ProviderName ?? string.Empty;
 343                if (provider.Contains("Sqlite", StringComparison.OrdinalIgnoreCase))
 44                {
 345                    await _context.Database.ExecuteSqlRawAsync(
 346                        "INSERT OR IGNORE INTO Shipments (ShipmentNumber) VALUES ({0})", shipmentNumber);
 47
 348                    var sqliteId = await _context.Database.SqlQueryRaw<int>(
 349                        "SELECT Id AS Value FROM Shipments WHERE ShipmentNumber = {0} LIMIT 1", shipmentNumber)
 350                        .SingleOrDefaultAsync();
 51
 352                    if (sqliteId > 0)
 353                        return sqliteId;
 54
 055                    sqliteId = await _context.Shipments
 056                        .AsNoTracking()
 057                        .Where(s => s.ShipmentNumber == shipmentNumber)
 058                        .Select(s => s.Id)
 059                        .FirstOrDefaultAsync();
 60
 061                    if (sqliteId > 0)
 062                        return sqliteId;
 63                }
 64                else
 65                {
 66                    // MySQL / MariaDB: атомарная вставка с игнорированием дубликатов по UX_Shipments_ShipmentNumber.
 67                    // Исключает выброс DbUpdateException и ошибки RelationalEventId.SaveChangesFailed (EventId 10000)
 68                    // при параллельном создании отправления в нескольких потоках / джобах (#1824, #1825).
 69                    // LOCK IN SHARE MODE гарантирует чтение актуальной строки даже в транзакциях REPEATABLE READ.
 070                    await _context.Database.ExecuteSqlRawAsync(
 071                        "INSERT IGNORE INTO `Shipments` (`ShipmentNumber`) VALUES ({0})", shipmentNumber);
 72
 073                    var mysqlId = await _context.Database.SqlQueryRaw<int>(
 074                        "SELECT `Id` AS `Value` FROM `Shipments` WHERE `ShipmentNumber` = {0} LOCK IN SHARE MODE", shipm
 075                        .SingleOrDefaultAsync();
 76
 077                    if (mysqlId > 0)
 078                        return mysqlId;
 79
 80                    // Fallback при задержке видимости или пробелах/collation
 081                    mysqlId = await _context.Database.SqlQueryRaw<int>(
 082                        "SELECT `Id` AS `Value` FROM `Shipments` WHERE TRIM(`ShipmentNumber`) = {0} LIMIT 1", shipmentNu
 083                        .SingleOrDefaultAsync();
 84
 085                    if (mysqlId > 0)
 086                        return mysqlId;
 87
 088                    mysqlId = await _context.Shipments
 089                        .AsNoTracking()
 090                        .Where(s => s.ShipmentNumber == shipmentNumber)
 091                        .Select(s => s.Id)
 092                        .FirstOrDefaultAsync();
 93
 094                    if (mysqlId > 0)
 095                        return mysqlId;
 96                }
 97
 098                throw new InvalidOperationException($"Не удалось получить или создать отправление '{shipmentNumber}'.");
 99            }
 100
 101            // Fallback исключительно для нереляционных провайдеров (UseInMemoryDatabase в тестах)
 102            try
 103            {
 2104                var newShipment = new Shipment { ShipmentNumber = shipmentNumber };
 2105                await _context.Shipments.AddAsync(newShipment);
 2106                await _context.SaveChangesAsync();
 2107                return newShipment.Id;
 108            }
 0109            catch (DbUpdateException)
 110            {
 0111                var failedEntry = _context.ChangeTracker.Entries<Shipment>()
 0112                    .FirstOrDefault(e => e.Entity.ShipmentNumber == shipmentNumber && e.State == EntityState.Added);
 0113                if (failedEntry != null)
 0114                    failedEntry.State = EntityState.Detached;
 115
 0116                var existing = await _context.Shipments
 0117                    .FirstOrDefaultAsync(s => s.ShipmentNumber == shipmentNumber);
 118
 0119                if (existing != null)
 0120                    return existing.Id;
 121
 0122                throw;
 123            }
 14124        }
 125
 126        /// <summary>
 127        /// Получает отправление по Id.
 128        /// </summary>
 129        public async Task<Shipment?> GetAsync(int id)
 130        {
 1131            return await _context.Shipments.FindAsync(id);
 1132        }
 133
 134        /// <summary>
 135        /// Получает отправление по номеру.
 136        /// </summary>
 137        public async Task<Shipment?> GetByNumberAsync(string shipmentNumber)
 138        {
 2139            if (string.IsNullOrWhiteSpace(shipmentNumber))
 0140                return null;
 141
 2142            shipmentNumber = shipmentNumber.Trim();
 2143            return await _context.Shipments
 2144                .FirstOrDefaultAsync(s => s.ShipmentNumber == shipmentNumber);
 2145        }
 146
 147        /// <summary>
 148        /// Получает все отправления.
 149        /// </summary>
 150        public async Task<List<Shipment>> GetAllAsync()
 151        {
 0152            return await _context.Shipments.ToListAsync();
 0153        }
 154
 155        /// <summary>
 156        /// Добавляет новое отправление.
 157        /// </summary>
 158        public async Task<int> AddAsync(Shipment shipment)
 159        {
 0160            await _context.Shipments.AddAsync(shipment);
 0161            return await _context.SaveChangesAsync();
 0162        }
 163
 164        /// <summary>
 165        /// Удаляет отправление.
 166        /// </summary>
 167        public async Task<int> DeleteAsync(Shipment shipment)
 168        {
 0169            _context.Shipments.Remove(shipment);
 0170            return await _context.SaveChangesAsync();
 0171        }
 172    }
 173}