Skip to content

Commit c3adf25

Browse files
fix: Ignore fixed settings in SaveObjectSettingsAsync() (#2835)
Co-authored-by: artem-dudarev <ad@virtoway.com>
1 parent 9b928ab commit c3adf25

File tree

3 files changed

+63
-68
lines changed

3 files changed

+63
-68
lines changed

src/VirtoCommerce.Platform.Core/Domain/IRepository.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ namespace VirtoCommerce.Platform.Core.Common
66
/// <summary>
77
/// Repository interface. Provides base interface for all repositories used in the framework.
88
/// </summary>
9-
public interface IRepository : IDisposable
10-
{
9+
public interface IRepository : IDisposable
10+
{
1111
/// <summary>
1212
/// Gets the unit of work. This class actually saves the data into underlying storage.
1313
/// </summary>
@@ -21,27 +21,27 @@ public interface IRepository : IDisposable
2121
/// </summary>
2222
/// <typeparam name="T"></typeparam>
2323
/// <param name="item">The item.</param>
24-
void Attach<T>(T item) where T : class;
25-
24+
void Attach<T>(T item) where T : class;
25+
2626
/// <summary>
2727
/// Adds the specified item to the context in the Added state. Meaning item will be created in the underlying storage.
2828
/// </summary>
2929
/// <typeparam name="T"></typeparam>
3030
/// <param name="item">The item.</param>
31-
void Add<T>(T item) where T : class;
31+
void Add<T>(T item) where T : class;
3232

3333
/// <summary>
3434
/// Updates the specified item. Marks the item for the update.
3535
/// </summary>
3636
/// <typeparam name="T"></typeparam>
3737
/// <param name="item">The item.</param>
38-
void Update<T>(T item) where T : class;
38+
void Update<T>(T item) where T : class;
3939

4040
/// <summary>
4141
/// Removes the specified item. Item marked for deletion and will be removed from the underlying storage on save.
4242
/// </summary>
4343
/// <typeparam name="T"></typeparam>
4444
/// <param name="item">The item.</param>
45-
void Remove<T>(T item) where T : class;
46-
}
45+
void Remove<T>(T item) where T : class;
46+
}
4747
}

src/VirtoCommerce.Platform.Data/Infrastructure/DbContextRepositoryBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ protected DbContextRepositoryBase(TContext dbContext, IUnitOfWork unitOfWork = n
1818
// Mitigations the breaking changes with cascade deletion introduced in EF Core 3.0
1919
// https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#cascade
2020
// The new CascadeTiming.Immediate that is used by default in EF Core 3.0 is lead wrong track as Added for Deleted dependent/child entities during
21-
// work of Patch method for data entities
21+
// work of Patch method for data entities
2222
DbContext.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
2323
DbContext.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
2424

src/VirtoCommerce.Platform.Data/Settings/SettingsManager.cs

+54-59
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace VirtoCommerce.Platform.Data.Settings
2121
{
2222
/// <summary>
2323
/// Provide next functionality to working with settings
24-
/// - Load setting metainformation from module manifest and database
24+
/// - Load settings meta information from module manifest and database
2525
/// - Deep load all settings for entity
2626
/// - Mass update all entity settings
2727
/// </summary>
@@ -32,7 +32,7 @@ public class SettingsManager : ISettingsManager
3232
private readonly IDictionary<string, SettingDescriptor> _registeredSettingsByNameDict = new Dictionary<string, SettingDescriptor>(StringComparer.OrdinalIgnoreCase).WithDefaultValue(null);
3333
private readonly IDictionary<string, IEnumerable<SettingDescriptor>> _registeredTypeSettingsByNameDict = new Dictionary<string, IEnumerable<SettingDescriptor>>(StringComparer.OrdinalIgnoreCase).WithDefaultValue(null);
3434
private readonly IEventPublisher _eventPublisher;
35-
private readonly IDictionary<string, ObjectSettingEntry> _fixedSettingsDict;
35+
private readonly Dictionary<string, ObjectSettingEntry> _fixedSettingsDict;
3636

3737
public SettingsManager(Func<IPlatformRepository> repositoryFactory,
3838
IPlatformMemoryCache memoryCache,
@@ -51,10 +51,8 @@ public SettingsManager(Func<IPlatformRepository> repositoryFactory,
5151

5252
public void RegisterSettingsForType(IEnumerable<SettingDescriptor> settings, string typeName)
5353
{
54-
if (settings == null)
55-
{
56-
throw new ArgumentNullException(nameof(settings));
57-
}
54+
ArgumentNullException.ThrowIfNull(settings);
55+
5856
var existTypeSettings = _registeredTypeSettingsByNameDict[typeName];
5957
if (existTypeSettings != null)
6058
{
@@ -65,17 +63,15 @@ public void RegisterSettingsForType(IEnumerable<SettingDescriptor> settings, str
6563

6664
public IEnumerable<SettingDescriptor> GetSettingsForType(string typeName)
6765
{
68-
return _registeredTypeSettingsByNameDict[typeName] ?? Enumerable.Empty<SettingDescriptor>();
66+
return _registeredTypeSettingsByNameDict[typeName] ?? [];
6967
}
7068

7169
public IEnumerable<SettingDescriptor> AllRegisteredSettings => _registeredSettingsByNameDict.Values;
7270

7371
public void RegisterSettings(IEnumerable<SettingDescriptor> settings, string moduleId = null)
7472
{
75-
if (settings == null)
76-
{
77-
throw new ArgumentNullException(nameof(settings));
78-
}
73+
ArgumentNullException.ThrowIfNull(settings);
74+
7975
foreach (var setting in settings)
8076
{
8177
setting.ModuleId = moduleId;
@@ -89,21 +85,18 @@ public void RegisterSettings(IEnumerable<SettingDescriptor> settings, string mod
8985

9086
public virtual async Task<ObjectSettingEntry> GetObjectSettingAsync(string name, string objectType = null, string objectId = null)
9187
{
92-
if (name == null)
93-
{
94-
throw new ArgumentNullException(nameof(name));
95-
}
96-
return (await GetObjectSettingsAsync(new[] { name }, objectType, objectId)).FirstOrDefault();
88+
ArgumentException.ThrowIfNullOrWhiteSpace(name);
89+
90+
return (await GetObjectSettingsAsync([name], objectType, objectId)).FirstOrDefault();
9791
}
9892

9993
public virtual async Task<IEnumerable<ObjectSettingEntry>> GetObjectSettingsAsync(IEnumerable<string> names, string objectType = null, string objectId = null)
10094
{
101-
if (names == null)
102-
{
103-
throw new ArgumentNullException(nameof(names));
104-
}
105-
var cacheKey = CacheKey.With(GetType(), "GetSettingByNamesAsync", string.Join(";", names), objectType, objectId);
106-
var result = await _memoryCache.GetOrCreateExclusiveAsync(cacheKey, async (cacheEntry) =>
95+
ArgumentNullException.ThrowIfNull(names);
96+
97+
var settingNames = names as string[] ?? names.ToArray();
98+
var cacheKey = CacheKey.With(GetType(), "GetSettingByNamesAsync", string.Join(";", settingNames), objectType, objectId);
99+
var result = await _memoryCache.GetOrCreateExclusiveAsync(cacheKey, async cacheEntry =>
107100
{
108101
var resultObjectSettings = new List<ObjectSettingEntry>();
109102
var dbStoredSettings = new List<SettingEntity>();
@@ -113,87 +106,89 @@ public virtual async Task<IEnumerable<ObjectSettingEntry>> GetObjectSettingsAsyn
113106
{
114107
repository.DisableChangesTracking();
115108
//try to load setting from db
116-
dbStoredSettings.AddRange(await repository.GetObjectSettingsByNamesAsync(names.ToArray(), objectType, objectId));
109+
dbStoredSettings.AddRange(await repository.GetObjectSettingsByNamesAsync(settingNames, objectType, objectId));
117110
}
118111

119-
foreach (var name in names)
112+
foreach (var name in settingNames)
120113
{
121-
var objectSetting = _fixedSettingsDict.ContainsKey(name) ?
122-
GetFixedSetting(name) :
123-
GetRegularSetting(name, dbStoredSettings, objectType, objectId);
114+
var objectSetting = _fixedSettingsDict.ContainsKey(name)
115+
? GetFixedSetting(name)
116+
: GetRegularSetting(name, dbStoredSettings, objectType, objectId);
124117

125118
resultObjectSettings.Add(objectSetting);
126119

127120
//Add cache expiration token for setting
128121
cacheEntry.AddExpirationToken(SettingsCacheRegion.CreateChangeToken(objectSetting));
129122
}
123+
130124
return resultObjectSettings;
131125
});
126+
132127
return result;
133128
}
134129

135130
public virtual async Task RemoveObjectSettingsAsync(IEnumerable<ObjectSettingEntry> objectSettings)
136131
{
137-
if (objectSettings == null)
138-
{
139-
throw new ArgumentNullException(nameof(objectSettings));
140-
}
132+
ArgumentNullException.ThrowIfNull(objectSettings);
133+
134+
var settingEntries = objectSettings as ObjectSettingEntry[] ?? objectSettings.ToArray();
141135
using (var repository = _repositoryFactory())
142136
{
143-
foreach (var objectSetting in objectSettings)
137+
foreach (var objectSetting in settingEntries)
144138
{
145-
var dbSetting = repository.Settings.FirstOrDefault(x => x.Name == objectSetting.Name && x.ObjectType == objectSetting.ObjectType && x.ObjectId == objectSetting.ObjectId);
139+
var dbSetting = repository.Settings.FirstOrDefault(x =>
140+
x.Name == objectSetting.Name && x.ObjectType == objectSetting.ObjectType &&
141+
x.ObjectId == objectSetting.ObjectId);
146142
if (dbSetting != null)
147143
{
148144
repository.Remove(dbSetting);
149145
}
150146
}
147+
151148
await repository.UnitOfWork.CommitAsync();
152-
ClearCache(objectSettings);
153149
}
150+
151+
ClearCache(settingEntries);
154152
}
155153

156154
public virtual async Task SaveObjectSettingsAsync(IEnumerable<ObjectSettingEntry> objectSettings)
157155
{
158-
if (objectSettings == null)
159-
{
160-
throw new ArgumentNullException(nameof(objectSettings));
161-
}
156+
ArgumentNullException.ThrowIfNull(objectSettings);
162157

163158
var changedEntries = new List<GenericChangedEntry<ObjectSettingEntry>>();
164159

160+
// Ignore unregistered settings, fixed settings, and settings without values
161+
var settings = objectSettings
162+
.Where(x => _registeredSettingsByNameDict.ContainsKey(x.Name) &&
163+
!_fixedSettingsDict.ContainsKey(x.Name) &&
164+
x.ItHasValues)
165+
.ToArray();
166+
165167
using (var repository = _repositoryFactory())
166168
{
167-
var settingNames = objectSettings.Select(x => x.Name).Distinct().ToArray();
168-
var alreadyExistDbSettings = (await repository.Settings
169+
var settingNames = settings.Select(x => x.Name).Distinct().ToArray();
170+
171+
var alreadyExistDbSettings = await repository.Settings
169172
.Include(s => s.SettingValues)
170173
.Where(x => settingNames.Contains(x.Name))
171174
.AsSplitQuery()
172-
.ToListAsync());
175+
.ToListAsync();
173176

174177
var validator = new ObjectSettingEntryValidator();
175-
foreach (var setting in objectSettings.Where(x => x.ItHasValues))
176-
{
177-
if (!validator.Validate(setting).IsValid)
178-
{
179-
throw new PlatformException($"Setting with name {setting.Name} is invalid");
180-
}
181-
182-
if (_fixedSettingsDict.ContainsKey(setting.Name))
183-
{
184-
throw new PlatformException($"Setting with name {setting.Name} is read only");
185-
}
186178

187-
// Skip when Setting is not registered
179+
foreach (var setting in settings)
180+
{
188181
var settingDescriptor = _registeredSettingsByNameDict[setting.Name];
189-
if (settingDescriptor == null)
182+
183+
if (!(await validator.ValidateAsync(setting)).IsValid)
190184
{
191-
continue;
185+
throw new PlatformException($"Setting with name {setting.Name} is invalid");
192186
}
193187

194188
// We need to convert resulting DB entities to model. Use ValueObject.Equals to find already saved setting entity from passed setting
195-
var originalEntity = alreadyExistDbSettings.Where(x => x.Name.EqualsInvariant(setting.Name))
196-
.FirstOrDefault(x => x.ToModel(new ObjectSettingEntry(settingDescriptor)).Equals(setting));
189+
var originalEntity = alreadyExistDbSettings.FirstOrDefault(x =>
190+
x.Name.EqualsIgnoreCase(setting.Name) &&
191+
x.ToModel(new ObjectSettingEntry(settingDescriptor)).Equals(setting));
197192

198193
var modifiedEntity = AbstractTypeFactory<SettingEntity>.TryCreateInstance().FromModel(setting);
199194

@@ -216,7 +211,7 @@ public virtual async Task SaveObjectSettingsAsync(IEnumerable<ObjectSettingEntry
216211
await repository.UnitOfWork.CommitAsync();
217212
}
218213

219-
ClearCache(objectSettings);
214+
ClearCache(settings);
220215

221216
await _eventPublisher.Publish(new ObjectSettingChangedEvent(changedEntries));
222217
}
@@ -245,7 +240,7 @@ protected virtual ObjectSettingEntry GetRegularSetting(string name, List<Setting
245240
ObjectType = objectType,
246241
ObjectId = objectId
247242
};
248-
var dbSetting = dbStoredSettings.FirstOrDefault(x => x.Name.EqualsInvariant(name));
243+
var dbSetting = dbStoredSettings.FirstOrDefault(x => x.Name.EqualsIgnoreCase(name));
249244
if (dbSetting != null)
250245
{
251246
objectSetting = dbSetting.ToModel(objectSetting);

0 commit comments

Comments
 (0)