|
6 | 6 | using EventLogExpert.Eventing.Providers; |
7 | 7 | using Microsoft.Extensions.DependencyInjection; |
8 | 8 | using System.CommandLine; |
| 9 | +using System.Text.RegularExpressions; |
9 | 10 |
|
10 | 11 | namespace EventLogExpert.EventDbTool; |
11 | 12 |
|
@@ -83,50 +84,94 @@ private void CreateDatabase(string path, string? source, string? filter, string? |
83 | 84 | return; |
84 | 85 | } |
85 | 86 |
|
86 | | - if (source is not null && !ProviderSource.TryValidate(source, Logger)) { return; } |
| 87 | + if (!RegexHelper.TryCreate(filter, Logger, out var regex)) { return; } |
87 | 88 |
|
88 | | - HashSet<string> skipProviderNames = new(StringComparer.OrdinalIgnoreCase); |
| 89 | + if (source is not null && !ProviderSource.TryValidate(source, Logger)) { return; } |
89 | 90 |
|
90 | | - if (!string.IsNullOrWhiteSpace(skipProvidersInFile)) |
| 91 | + try |
91 | 92 | { |
92 | | - if (!ProviderSource.TryValidate(skipProvidersInFile, Logger)) { return; } |
| 93 | + HashSet<string> skipProviderNames = new(StringComparer.OrdinalIgnoreCase); |
93 | 94 |
|
94 | | - foreach (var name in ProviderSource.LoadProviderNames(skipProvidersInFile, Logger)) |
| 95 | + if (!string.IsNullOrWhiteSpace(skipProvidersInFile)) |
95 | 96 | { |
96 | | - skipProviderNames.Add(name); |
| 97 | + if (!ProviderSource.TryValidate(skipProvidersInFile, Logger)) { return; } |
| 98 | + |
| 99 | + foreach (var name in ProviderSource.LoadProviderNames(skipProvidersInFile, Logger)) |
| 100 | + { |
| 101 | + skipProviderNames.Add(name); |
| 102 | + } |
| 103 | + |
| 104 | + Logger.Info($"Found {skipProviderNames.Count} providers in {skipProvidersInFile}. These will not be included in the new database."); |
97 | 105 | } |
98 | 106 |
|
99 | | - Logger.Info($"Found {skipProviderNames.Count} providers in {skipProvidersInFile}. These will not be included in the new database."); |
100 | | - } |
| 107 | + // Load provider names first (cheap string-only query) for the empty check and header |
| 108 | + // formatting. This avoids materializing all ProviderDetails (with large compressed |
| 109 | + // payloads) just to compute the column widths. |
| 110 | + IReadOnlyList<string> providerNames = source is null |
| 111 | + ? GetLocalProviderNames(regex) |
| 112 | + .Where(n => !skipProviderNames.Contains(n)).ToList() |
| 113 | + : ProviderSource.LoadProviderNames(source, Logger, regex) |
| 114 | + .Where(n => !skipProviderNames.Contains(n)).ToList(); |
101 | 115 |
|
102 | | - IEnumerable<ProviderDetails> providersToAdd = source is null |
103 | | - ? LoadLocalProviders(filter, skipProviderNames) |
104 | | - : ProviderSource.LoadProviders(source, Logger, filter, skipProviderNames); |
| 116 | + if (providerNames.Count == 0) |
| 117 | + { |
| 118 | + Logger.Warn($"No providers to add to the new database."); |
| 119 | + return; |
| 120 | + } |
105 | 121 |
|
106 | | - var providersNotSkipped = providersToAdd.ToList(); |
| 122 | + LogProviderDetailHeader(providerNames); |
107 | 123 |
|
108 | | - if (providersNotSkipped.Count == 0) |
109 | | - { |
110 | | - Logger.Warn($"No providers to add to the new database."); |
111 | | - return; |
112 | | - } |
| 124 | + // Defer creating the DbContext (and therefore the .db file on disk) until we have |
| 125 | + // at least one provider to persist. This prevents leaving an empty database behind |
| 126 | + // when no provider details could be resolved (e.g., .evtx without LocaleMetaData). |
| 127 | + EventProviderDbContext? dbContext = null; |
| 128 | + |
| 129 | + try |
| 130 | + { |
| 131 | + // Stream details directly into the DbContext. Batch saves prevent the change tracker |
| 132 | + // from accumulating all entities in memory at once. |
| 133 | + const int batchSize = 100; |
| 134 | + var count = 0; |
113 | 135 |
|
114 | | - using var dbContext = new EventProviderDbContext(path, false, Logger); |
| 136 | + IEnumerable<ProviderDetails> providersToAdd = source is null |
| 137 | + ? LoadLocalProviders(regex, skipProviderNames) |
| 138 | + : ProviderSource.LoadProviders(source, Logger, regex, skipProviderNames); |
115 | 139 |
|
116 | | - LogProviderDetailHeader(providersNotSkipped.Select(p => p.ProviderName)); |
| 140 | + foreach (var details in providersToAdd) |
| 141 | + { |
| 142 | + dbContext ??= new EventProviderDbContext(path, false, Logger); |
| 143 | + dbContext.ProviderDetails.Add(details); |
| 144 | + LogProviderDetails(details); |
| 145 | + count++; |
117 | 146 |
|
118 | | - foreach (var details in providersNotSkipped) |
119 | | - { |
120 | | - dbContext.ProviderDetails.Add(details); |
121 | | - LogProviderDetails(details); |
122 | | - } |
| 147 | + if (count % batchSize != 0) { continue; } |
| 148 | + dbContext.SaveChanges(); |
| 149 | + dbContext.ChangeTracker.Clear(); |
| 150 | + } |
123 | 151 |
|
124 | | - Logger.Info($""); |
125 | | - Logger.Info($"Saving database. Please wait..."); |
| 152 | + if (dbContext is null) |
| 153 | + { |
| 154 | + Logger.Warn($"No provider details could be resolved from the source. Database was not created."); |
126 | 155 |
|
127 | | - dbContext.SaveChanges(); |
| 156 | + return; |
| 157 | + } |
128 | 158 |
|
129 | | - Logger.Info($"Done!"); |
| 159 | + Logger.Info($""); |
| 160 | + Logger.Info($"Saving database. Please wait..."); |
| 161 | + |
| 162 | + dbContext.SaveChanges(); |
| 163 | + |
| 164 | + Logger.Info($"Done!"); |
| 165 | + } |
| 166 | + finally |
| 167 | + { |
| 168 | + dbContext?.Dispose(); |
| 169 | + } |
| 170 | + } |
| 171 | + catch (RegexMatchTimeoutException) |
| 172 | + { |
| 173 | + Logger.Error($"The --filter regex timed out. The pattern may cause catastrophic backtracking."); |
| 174 | + } |
130 | 175 | } |
131 | 176 |
|
132 | 177 | } |
0 commit comments