using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using MiGu.Server.Dashboard; using MiGu.Server.Wms; using MiGu.Server.SimpleFields; namespace MiGu.Server.Persistence; public sealed class PlatformDbContext : DbContext { public PlatformDbContext(DbContextOptions options) : base(options) { } public DbSet Warehouses => Set(); public DbSet WarehouseAreas => Set(); public DbSet Storages => Set(); public DbSet Containers => Set(); public DbSet MaterialTypes => Set(); public DbSet Materials => Set(); public DbSet ContainerLocations => Set(); public DbSet ContainerMaterials => Set(); public DbSet StockEvents => Set(); public DbSet ContainerLocationHistories => Set(); public DbSet ContainerMaterialHistories => Set(); public DbSet WmsTransportRules => Set(); public DbSet WmsTransportTasks => Set(); public DbSet WmsTransportReservations => Set(); public DbSet WmsTransportTaskHistories => Set(); public DbSet SimpleFields => Set(); public DbSet UserDashboardShortcuts => Set(); public DbSet CdmTasks => Set(); public DbSet VehicleAlarms => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { var guid = new ValueConverter( v => v.ToString("D"), v => Guid.Parse(v)); var nullableGuid = new ValueConverter( v => v.HasValue ? v.Value.ToString("D") : null, v => string.IsNullOrWhiteSpace(v) ? null : Guid.Parse(v)); var dateTimeOffset = new ValueConverter( v => v.UtcDateTime.ToString("O"), v => DateTimeOffset.Parse(v)); var nullableDateTimeOffset = new ValueConverter( v => v.HasValue ? v.Value.UtcDateTime.ToString("O") : null, v => string.IsNullOrWhiteSpace(v) ? null : DateTimeOffset.Parse(v)); foreach (var entity in modelBuilder.Model.GetEntityTypes()) { foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(Guid))) modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(guid).HasMaxLength(36); foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(Guid?))) modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(nullableGuid).HasMaxLength(36); foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset))) modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(dateTimeOffset).HasMaxLength(40); foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset?))) modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(nullableDateTimeOffset).HasMaxLength(40); } ConfigureEntityBase(modelBuilder, "wms_warehouses"); ConfigureEntityBase(modelBuilder, "wms_areas"); ConfigureEntityBase(modelBuilder, "wms_storages"); ConfigureEntityBase(modelBuilder, "wms_containers"); ConfigureEntityBase(modelBuilder, "wms_material_types"); ConfigureEntityBase(modelBuilder, "wms_materials"); ConfigureEntityBase(modelBuilder, "wms_container_locations"); ConfigureEntityBase(modelBuilder, "wms_container_materials"); ConfigureEntityBase(modelBuilder, "wms_transport_rules"); ConfigureEntityBase(modelBuilder, "wms_transport_tasks"); ConfigureEntityBase(modelBuilder, "wms_transport_reservations"); ConfigureHistory(modelBuilder, "wms_container_location_history"); ConfigureHistory(modelBuilder, "wms_container_material_history"); ConfigureStockEvent(modelBuilder); ConfigureTransportTaskHistory(modelBuilder); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.WarehouseId); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.AreaId); modelBuilder.Entity().HasIndex(x => x.Barcode); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => x.Barcode); modelBuilder.Entity().HasIndex(x => new { x.LifecycleStatus, x.UpdatedAt }); modelBuilder.Entity().HasIndex(x => x.TypeCode); modelBuilder.Entity().HasIndex(x => x.ContainerId).IsUnique(); // 库位占用 1:1:同一 Storage LocationId 同时只能有一条未删除记录(Car 等其它类型不限) modelBuilder.Entity() .HasIndex(x => x.LocationId) .IsUnique() .HasFilter("LocationType = 'Storage' AND IsDeleted = 0") .HasDatabaseName("IX_wms_container_locations_StorageLocationId"); modelBuilder.Entity().HasIndex(x => new { x.LocationType, x.LocationId }); modelBuilder.Entity().HasIndex(x => x.MaterialId).IsUnique(); modelBuilder.Entity().HasIndex(x => x.ContainerId); modelBuilder.Entity().HasIndex(x => x.Code).IsUnique(); modelBuilder.Entity().HasIndex(x => new { x.TriggerType, x.Enabled, x.Priority }); modelBuilder.Entity().HasIndex(x => x.Status); modelBuilder.Entity().HasIndex(x => x.ContainerId); modelBuilder.Entity().HasIndex(x => x.TargetStorageId); modelBuilder.Entity().HasIndex(x => new { x.ContainerId, x.Status }); modelBuilder.Entity().HasIndex(x => new { x.TargetStorageId, x.Status }); modelBuilder.Entity().Property(x => x.Quantity).HasPrecision(18, 4); modelBuilder.Entity().Property(x => x.Quantity).HasPrecision(18, 4); modelBuilder.Entity().Property(x => x.QuantityDelta).HasPrecision(18, 4); ConfigureSimpleField(modelBuilder); ConfigureUserDashboardShortcut(modelBuilder); ConfigureCdmTask(modelBuilder); ConfigureVehicleAlarm(modelBuilder); } private static void ConfigureVehicleAlarm(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("vehicle_alarms"); e.HasKey(x => x.Id); e.Property(x => x.Id).HasColumnName("id").HasMaxLength(36); e.Property(x => x.CarId).HasColumnName("car_id"); e.Property(x => x.CarName).HasColumnName("car_name").HasMaxLength(128); e.Property(x => x.Info).HasColumnName("info").HasColumnType("text"); e.Property(x => x.Level).HasColumnName("level"); e.Property(x => x.Status).HasColumnName("status").HasMaxLength(16); e.Property(x => x.FirstAt).HasColumnName("first_at"); e.Property(x => x.LastAt).HasColumnName("last_at"); e.Property(x => x.ResolvedAt).HasColumnName("resolved_at").IsRequired(false); e.Property(x => x.DurationSecs).HasColumnName("duration_secs").IsRequired(false); e.Property(x => x.Acknowledged).HasColumnName("acknowledged"); e.Property(x => x.AcknowledgedAt).HasColumnName("acknowledged_at").IsRequired(false); e.Property(x => x.AcknowledgedBy).HasColumnName("acknowledged_by").HasMaxLength(128).IsRequired(false); e.HasIndex(x => new { x.CarId, x.Status }); e.HasIndex(x => x.Status); e.HasIndex(x => x.FirstAt); } private static void ConfigureCdmTask(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("cdm_tasks"); e.HasKey(x => x.Id); e.Property(x => x.Id).HasColumnName("id").HasMaxLength(64); e.Property(x => x.TaskId).HasColumnName("task_id").HasMaxLength(128).IsRequired(false); e.Property(x => x.MissionId).HasColumnName("mission_id"); e.Property(x => x.MissionName).HasColumnName("mission_name").HasMaxLength(128); e.Property(x => x.MissionTypeName).HasColumnName("mission_type").HasMaxLength(128); e.Property(x => x.SrcSiteId).HasColumnName("src_site_id"); e.Property(x => x.SrcLabel).HasColumnName("src_label").HasMaxLength(256); e.Property(x => x.DstSiteId).HasColumnName("dst_site_id"); e.Property(x => x.DstLabel).HasColumnName("dst_label").HasMaxLength(256); e.Property(x => x.Status).HasColumnName("status").HasMaxLength(32); e.Property(x => x.StatusCode).HasColumnName("status_code").HasMaxLength(32); e.Property(x => x.CarId).HasColumnName("car_id").IsRequired(false); e.Property(x => x.CarName).HasColumnName("car_name").HasMaxLength(128).IsRequired(false); e.Property(x => x.Priority).HasColumnName("priority"); e.Property(x => x.CreateTime).HasColumnName("create_time").HasMaxLength(40).IsRequired(false); e.Property(x => x.StartTime).HasColumnName("start_time").HasMaxLength(40).IsRequired(false); e.Property(x => x.FinishTime).HasColumnName("finish_time").HasMaxLength(40).IsRequired(false); e.Property(x => x.StuckReason).HasColumnName("stuck_reason").HasMaxLength(512).IsRequired(false); e.Property(x => x.Overdue).HasColumnName("overdue"); e.Property(x => x.FirstSeenAt).HasColumnName("first_seen_at"); e.Property(x => x.LastSeenAt).HasColumnName("last_seen_at"); e.HasIndex(x => x.StatusCode); e.HasIndex(x => x.CreateTime); } private static void ConfigureUserDashboardShortcut(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("user_dashboard_shortcuts"); e.HasKey(x => new { x.UserId, x.Scope }); e.Property(x => x.UserId).HasColumnName("user_id").HasMaxLength(64); e.Property(x => x.Scope).HasColumnName("scope").HasMaxLength(32); e.Property(x => x.KeysJson).HasColumnName("keys_json").HasColumnType("text"); var dateTime = new ValueConverter( v => v.UtcDateTime.ToString("O"), v => DateTimeOffset.Parse(v)); e.Property(x => x.UpdatedAt).HasColumnName("updated_at").HasConversion(dateTime).HasMaxLength(40); } private static void ConfigureSimpleField(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("simple_fields"); e.HasKey(x => x.Id); e.Property(x => x.Id).HasColumnName("id"); e.Property(x => x.CarType).HasColumnName("car_type").HasMaxLength(64); e.Property(x => x.FieldType).HasColumnName("field_type").HasMaxLength(64); e.Property(x => x.Key).HasColumnName("key").HasMaxLength(128); e.Property(x => x.Value).HasColumnName("value"); e.Property(x => x.DataType).HasColumnName("data_type").HasMaxLength(128); e.Property(x => x.Chinese).HasColumnName("chinese").HasMaxLength(256).IsRequired(false); e.Property(x => x.English).HasColumnName("english").HasMaxLength(256).IsRequired(false); e.Property(x => x.Other).HasColumnName("other").HasMaxLength(512); e.Property(x => x.IsDefault).HasColumnName("is_default"); var dateTime = new ValueConverter( v => SimpleFieldDateTime.ToStorage(v), v => SimpleFieldDateTime.FromStorage(v)); e.Property(x => x.CreateTime).HasColumnName("create_time").HasConversion(dateTime).HasMaxLength(19); e.Property(x => x.UpdateTime).HasColumnName("update_time").HasConversion(dateTime).HasMaxLength(19); e.HasIndex(x => new { x.CarType, x.FieldType, x.Key }).IsUnique(); } public override int SaveChanges(bool acceptAllChangesOnSuccess) { StampEntities(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { StampEntities(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void StampEntities() { var now = DateTimeOffset.UtcNow; foreach (var e in ChangeTracker.Entries()) { if (e.State == EntityState.Added) { if (e.Entity.Id == Guid.Empty) e.Entity.Id = Guid.NewGuid(); e.Entity.CreatedAt = now; e.Entity.UpdatedAt = now; e.Entity.Version = Math.Max(1, e.Entity.Version); if (string.IsNullOrWhiteSpace(e.Entity.Extend)) e.Entity.Extend = "{}"; } else if (e.State == EntityState.Modified) { e.Entity.UpdatedAt = now; e.Entity.Version += 1; if (string.IsNullOrWhiteSpace(e.Entity.Extend)) e.Entity.Extend = "{}"; } } } private static void ConfigureEntityBase(ModelBuilder modelBuilder, string table) where T : EntityBase { var e = modelBuilder.Entity(); e.ToTable(table); e.HasKey(x => x.Id); e.Property(x => x.CreatedBy).HasMaxLength(128); e.Property(x => x.UpdatedBy).HasMaxLength(128); e.Property(x => x.DeletedBy).HasMaxLength(128); e.Property(x => x.Remark).HasMaxLength(1000); e.Property(x => x.Extend).HasColumnType("text"); e.HasQueryFilter(x => !x.IsDeleted); } private static void ConfigureHistory(ModelBuilder modelBuilder, string table) where T : WarehouseHistoryBase { var e = modelBuilder.Entity(); e.ToTable(table); e.HasKey(x => x.Id); e.Property(x => x.EventType).HasMaxLength(64); e.Property(x => x.BeforeJson).HasColumnType("text"); e.Property(x => x.AfterJson).HasColumnType("text"); e.Property(x => x.Operator).HasMaxLength(128); e.Property(x => x.Source).HasMaxLength(32); e.Property(x => x.Reason).HasMaxLength(500); e.Property(x => x.Remark).HasMaxLength(1000); e.Property(x => x.Extend).HasColumnType("text"); e.HasIndex(x => x.ContainerId); e.HasIndex(x => x.OperatedAt); e.HasIndex(x => x.EventType); } private static void ConfigureStockEvent(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("wms_stock_events"); e.HasKey(x => x.Id); e.Property(x => x.EventType).HasMaxLength(32); e.Property(x => x.MaterialCode).HasMaxLength(64); e.Property(x => x.MaterialName).HasMaxLength(128); e.Property(x => x.MaterialBarcode).HasMaxLength(128); e.Property(x => x.MaterialTypeCode).HasMaxLength(64); e.Property(x => x.ContainerCode).HasMaxLength(64); e.Property(x => x.ContainerName).HasMaxLength(128); e.Property(x => x.StorageCode).HasMaxLength(64); e.Property(x => x.StorageName).HasMaxLength(128); e.Property(x => x.AreaCode).HasMaxLength(64); e.Property(x => x.FromStorageCode).HasMaxLength(64); e.Property(x => x.FromStorageName).HasMaxLength(128); e.Property(x => x.ToStorageCode).HasMaxLength(64); e.Property(x => x.ToStorageName).HasMaxLength(128); e.Property(x => x.RefType).HasMaxLength(64); e.Property(x => x.RefCode).HasMaxLength(64); e.Property(x => x.Operator).HasMaxLength(128); e.Property(x => x.Reason).HasMaxLength(500); e.HasIndex(x => x.OperatedAt); e.HasIndex(x => x.EventType); e.HasIndex(x => x.MaterialId); e.HasIndex(x => x.ContainerId); } private static void ConfigureTransportTaskHistory(ModelBuilder modelBuilder) { var e = modelBuilder.Entity(); e.ToTable("wms_transport_task_history"); e.HasKey(x => x.Id); e.Property(x => x.FromStatus).HasMaxLength(32); e.Property(x => x.ToStatus).HasMaxLength(32); e.Property(x => x.Operator).HasMaxLength(128); e.Property(x => x.Reason).HasMaxLength(500); e.Property(x => x.ErrorMessage).HasMaxLength(1000); e.Property(x => x.SnapshotJson).HasColumnType("text"); e.HasIndex(x => x.TaskId); e.HasIndex(x => x.OperatedAt); } }