diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 2d9fc436..4e4ba8be 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -3434,6 +3434,9 @@ Kurallar: - **Anahtar dosyada durur.** Insert varsayılanları anahtarı `@NEWID` ile üretir; `ListFormSeedDataApplier` dosyadaki anahtarı geri yazar, böylece aynı dosya birden çok kez uygulandığında kopya kayıt oluşmaz. Elle dosya yazarken anahtarı deterministik üret (ör. koddan türeyen GUID). +- **Identity anahtarlı tablolarda satır anahtarsız yazılır.** `Id` alanı dosyada yoksa anahtar + veritabanına bırakılır; satırın var olup olmadığı dosyadaki **tüm alan değerlerinin** + (tenant kapsamı dahil) eşleşmesine bakılarak belirlenir. - **Uygulama yalnızca ekler:** anahtarı veritabanında bulunmayan satırlar `INSERT` edilir; var olan kayıt güncellenmez (her migrate'te ekrandaki güncel değerler dosyadaki eskisiyle ezilmesin diye) ve dosyada olmayan satır silinmez. diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs index 7bb16ed3..8c2d4c5f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs @@ -1145,6 +1145,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.GlobalSearch)), KeyFieldName = "Id", KeyFieldDbSourceType = DbType.Int32, + SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), + SeedSyncInsert = false, + SeedSyncUpdate = false, + SeedSyncDelete = false, SortMode = GridOptions.SortModeSingle, FilterRowJson = DefaultFilterRowJson(), HeaderFilterJson = DefaultHeaderFilterJson(), diff --git a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs index f03d3879..18ba6d06 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -138,7 +138,11 @@ public class ListFormSeedDataApplier( } } - /// Satiri ekler; kayit eklendiyse true, ayni anahtarli kayit zaten varsa false doner. + /// + /// Satiri ekler; kayit eklendiyse true, ayni kayit zaten varsa false doner. + /// Dosyada anahtar yoksa (identity kolonlu tablolar) satir diger alanlarinin tamami uzerinden + /// eslesir ve anahtar veritabanina biraktirilir. + /// private async Task ApplyRowAsync( ListForm listForm, List fields, @@ -154,37 +158,59 @@ public class ListFormSeedDataApplier( row.GetValueOrDefault(keyFieldName)); object[] keys = [keyValue]; + var hasKey = keyValue != null; + + var insertParameters = BuildParameters(fields, row, keyFieldName, includeKey: hasKey); // Var olan kayda dokunulmaz: veritabanindaki guncel deger, dosyadaki eski degerle ezilmemeli. - if (await ExistsAsync(listForm, keyFieldName, keyValue, repository, connectionString)) + var match = hasKey + ? new Dictionary { [keyFieldName] = keyValue } + : new Dictionary(insertParameters); + + if (!hasKey && PlatformConsts.IsMultiTenant && listForm.IsTenant) + { + match["TenantId"] = currentTenant.Id; + } + + if (await ExistsAsync(listForm, match, repository, connectionString)) { return false; } - var insertParameters = BuildParameters(fields, row, keyFieldName, includeKey: true); await MergeDefaultsAsync(listForm, fields, OperationEnum.Insert, keys, insertParameters); - // Insert varsayilanlari anahtari @NEWID ile uretir; dosyadaki anahtar korunmazsa ayni - // dosya her uygulandiginda yeni kayit olusurdu. - insertParameters[keyFieldName] = keyValue; + if (hasKey) + { + // Insert varsayilanlari anahtari @NEWID ile uretir; dosyadaki anahtar korunmazsa ayni + // dosya her uygulandiginda yeni kayit olusurdu. + insertParameters[keyFieldName] = keyValue; + } + else + { + // Anahtari veritabani uretir; parametre listesinde kalirsa identity kolonuna + // acik deger yazilmaya calisilir. + insertParameters.Remove(keyFieldName); + } var insertSql = queryManager.GenerateQuery(listForm, fields, insertParameters, OperationEnum.Insert, dataSourceType, keys); await repository.ExecuteAsync(insertSql, connectionString, insertParameters); return true; } - /// Ayni anahtara sahip bir kayit hedef tabloda var mi. + /// Verilen alan degerleriyle eslesen bir kayit hedef tabloda var mi. private static async Task ExistsAsync( ListForm listForm, - string keyFieldName, - object keyValue, + Dictionary match, IDynamicDataRepository repository, string connectionString) { - var sql = $"SELECT COUNT(1) FROM \"{listForm.SelectCommand}\" WHERE \"{keyFieldName}\" = @{keyFieldName}"; - var parameters = new Dictionary { [keyFieldName] = keyValue }; + var where = string.Join( + " AND ", + match.Select(a => a.Value == null ? $"\"{a.Key}\" IS NULL" : $"\"{a.Key}\" = @{a.Key}")); - return await repository.ExecuteScalarAsync(sql, connectionString, parameters) > 0; + var sql = $"SELECT COUNT(1) FROM \"{listForm.SelectCommand}\" WHERE {where}"; + + return await repository.ExecuteScalarAsync(sql, connectionString, match) > 0; } /// diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index bda2ec50..bccf6a89 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -1,27 +1,4 @@ { - "GlobalSearch": [ - { - "system": "Platform", - "group": "Setting Definitions", - "term": "Abp.Account.Captcha.EndPoint", - "weight": "0.2", - "url": "/admin/list/App.SettingDefinitions" - }, - { - "system": "Platform", - "group": "Setting Definitions", - "term": "Abp.Account.IsSelfRegistrationEnabled", - "weight": "0.8", - "url": "/admin/list/App.SettingDefinitions" - }, - { - "system": "Platform", - "group": "Setting Definitions", - "term": "Abp.Account.IsSelfRegistrationEnabled", - "weight": "0.7", - "url": "/admin/list/App.SettingDefinitions" - } - ], "ForumCategories": [ { "Name": "Genel Tartışma", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs index df8b99f6..c7887fdc 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs @@ -21,7 +21,6 @@ namespace Sozsoft.Platform.Data.Seeds; public class TenantSeederDto { //Saas - public List GlobalSearch { get; set; } public List CustomEndpoints { get; set; } public List Sequences { get; set; } @@ -224,15 +223,6 @@ public class DepartmentSeedDto public string ParentName { get; set; } } -public class GlobalSearchSeedDto -{ - public string System { get; set; } - public string Group { get; set; } - public string Term { get; set; } - public string Weight { get; set; } - public string Url { get; set; } -} - public class UomCategorySeedDto { public Guid Id { get; set; } @@ -430,7 +420,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency { private readonly ILookupNormalizer _lookupNormalizer; private readonly IRepository _branchRepository; - private readonly IRepository _globalSearch; private readonly IRepository _customEndpointRepository; private readonly IRepository _reportCategoriesRepository; private readonly IRepository _reportTemplatesRepository; @@ -471,7 +460,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency ILookupNormalizer lookupNormalizer, IIdentityUserRepository repositoryUser, IRepository branchRepository, - IRepository globalSearch, IRepository uomCategoryRepository, IRepository uomRepository, IRepository skillTypeRepository, @@ -510,7 +498,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency _lookupNormalizer = lookupNormalizer; _repositoryUser = repositoryUser; _branchRepository = branchRepository; - _globalSearch = globalSearch; _uomCategoryRepository = uomCategoryRepository; _uomRepository = uomRepository; _skillTypeRepository = skillTypeRepository; @@ -559,21 +546,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - foreach (var item in items.GlobalSearch) - { - if (!await _globalSearch.AnyAsync(x => x.System == item.System && x.Group == item.Group && x.Term == item.Term)) - { - await _globalSearch.InsertAsync(new GlobalSearch - { - System = item.System, - Group = item.Group, - Term = item.Term, - Weight = item.Weight, - Url = item.Url - }); - } - } - foreach (var item in items.UomCategories) { var exists = await _uomCategoryRepository.AnyAsync(x => x.Name == item.Name); diff --git a/configs/seeds/host/data/App.Definitions.GlobalSearch.json b/configs/seeds/host/data/App.Definitions.GlobalSearch.json new file mode 100644 index 00000000..4d1f31e2 --- /dev/null +++ b/configs/seeds/host/data/App.Definitions.GlobalSearch.json @@ -0,0 +1,28 @@ +{ + "ListFormCode": "App.Definitions.GlobalSearch", + "KeyFieldName": "Id", + "GeneratedAt": "2026-09-04T00:00:00Z", + "Rows": [ + { + "System": "Platform", + "Group": "Setting Definitions", + "Term": "Abp.Account.Captcha.EndPoint", + "Url": "/admin/list/App.SettingDefinitions", + "Weight": "0.2" + }, + { + "System": "Platform", + "Group": "Setting Definitions", + "Term": "Abp.Account.IsSelfRegistrationEnabled", + "Url": "/admin/list/App.SettingDefinitions", + "Weight": "0.8" + }, + { + "System": "Platform", + "Group": "Setting Definitions", + "Term": "Abp.Account.IsSelfRegistrationEnabled", + "Url": "/admin/list/App.SettingDefinitions", + "Weight": "0.7" + } + ] +}