Skip to content

Commit 51d1a2a

Browse files
authored
Usermetadata refactor (#70)
2 parents 85a4e77 + f1b1c0e commit 51d1a2a

32 files changed

+5197
-330
lines changed

src/AnalyticsEngine/Common/Entities/LookupCaches/Discrete/CallModalityCache.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@
44

55
namespace Common.Entities.LookupCaches
66
{
7-
internal class CallModalityCache : DBLookupCache<CallModality>
7+
internal class CallModalityCache : DBLookupCacheForEntityWithName<CallModality>
88
{
99
public CallModalityCache(AnalyticsEntitiesContext context) : base(context) { }
1010

1111
public override DbSet<CallModality> EntityStore => this.DB.CallModalities;
12-
13-
public async override Task<CallModality> Load(string searchKey)
14-
{
15-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchKey);
16-
}
1712
}
1813
}

src/AnalyticsEngine/Common/Entities/LookupCaches/Discrete/CallTypeCache.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@
44

55
namespace Common.Entities.LookupCaches
66
{
7-
internal class CallTypeCache : DBLookupCache<CallType>
7+
internal class CallTypeCache : DBLookupCacheForEntityWithName<CallType>
88
{
99
public CallTypeCache(AnalyticsEntitiesContext context) : base(context) { }
1010

1111
public override DbSet<CallType> EntityStore => this.DB.CallTypes;
12-
13-
public async override Task<CallType> Load(string searchKey)
14-
{
15-
return await EntityStore.FirstOrDefaultAsync(t => t.Name == searchKey);
16-
}
1712
}
1813
}
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
using System.Data.Entity;
2-
using System.Threading.Tasks;
32

43
namespace Common.Entities.LookupCaches
54
{
6-
public class CompanyNameCache : DBLookupCache<CompanyName>
5+
public class CompanyNameCache : DBLookupCacheForEntityWithName<CompanyName>
76
{
87
public CompanyNameCache(AnalyticsEntitiesContext context) : base(context) { }
98

109
public override DbSet<CompanyName> EntityStore => this.DB.CompanyNames;
11-
12-
public async override Task<CompanyName> Load(string searchName)
13-
{
14-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchName);
15-
}
1610
}
1711
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
using System.Data.Entity;
2+
using System.Linq;
23
using System.Threading.Tasks;
34

45
namespace Common.Entities.LookupCaches
56
{
6-
public class CountryOrRegionCache : DBLookupCache<CountryOrRegion>
7+
public class CountryOrRegionCache : DBLookupCacheForEntityWithName<CountryOrRegion>
78
{
89
public CountryOrRegionCache(AnalyticsEntitiesContext context) : base(context) { }
910

1011
public override DbSet<CountryOrRegion> EntityStore => this.DB.CountryOrRegions;
11-
12-
public async override Task<CountryOrRegion> Load(string searchName)
13-
{
14-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchName);
15-
}
1612
}
17-
1813
}

src/AnalyticsEngine/Common/Entities/LookupCaches/Discrete/DBLookupCache.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using DataUtils;
22
using System;
33
using System.Data.Entity;
4+
using System.Data.Entity.Infrastructure;
5+
using System.Data.SqlClient;
6+
using System.Linq;
47
using System.Threading.Tasks;
58

69
namespace Common.Entities
@@ -50,14 +53,46 @@ public async virtual Task<T> GetOrCreateNewResource(string key, T newTemplate, b
5053

5154
return await base.GetResource(key, async () =>
5255
{
53-
NewObjectCreating?.Invoke(this, newTemplate);
56+
try
57+
{
58+
NewObjectCreating?.Invoke(this, newTemplate);
5459

55-
this.EntityStore.Add(newTemplate);
56-
if (commitChangeOnSaveNew)
60+
this.EntityStore.Add(newTemplate);
61+
if (commitChangeOnSaveNew)
62+
{
63+
await DB.SaveChangesAsync();
64+
}
65+
return newTemplate;
66+
}
67+
catch (DbUpdateException ex)
5768
{
58-
await DB.SaveChangesAsync();
69+
// Handle duplicate key constraint violations that can occur in batch processing scenarios
70+
// Check if it's a unique constraint/index violation
71+
var sqlException = ex.InnerException?.InnerException as SqlException;
72+
if (sqlException != null && (sqlException.Number == 2601 || sqlException.Number == 2627))
73+
{
74+
// SQL Error 2601: Cannot insert duplicate key row with unique index
75+
// SQL Error 2627: Violation of %ls constraint '%.*ls'. Cannot insert duplicate key
76+
77+
// Remove the failed entity from context to prevent further issues
78+
DB.Entry(newTemplate).State = EntityState.Detached;
79+
80+
// Try to reload from database - another batch may have inserted it
81+
var existing = await this.Load(key);
82+
if (existing != null)
83+
{
84+
return existing;
85+
}
86+
87+
// If still not found, this is an unexpected state - rethrow
88+
throw new InvalidOperationException(
89+
$"Duplicate key constraint violation for lookup '{typeof(T).Name}' with key '{key}', but entity not found in database after reload.",
90+
ex);
91+
}
92+
93+
// Not a duplicate key error, rethrow
94+
throw;
5995
}
60-
return newTemplate;
6196
});
6297
}
6398

@@ -66,4 +101,18 @@ public async virtual Task<T> GetOrCreateNewResource(string key, T newTemplate, b
66101

67102
}
68103

104+
public abstract class DBLookupCacheForEntityWithName<T> : DBLookupCache<T> where T : AbstractEFEntityWithName
105+
{
106+
protected DBLookupCacheForEntityWithName(AnalyticsEntitiesContext context) : base(context)
107+
{
108+
}
109+
110+
111+
public async override Task<T> Load(string searchName)
112+
{
113+
// Use FirstOrDefaultAsync instead of SingleOrDefaultAsync to handle existing duplicate records gracefully
114+
// Order by ID to ensure consistent results if duplicates exist
115+
return await EntityStore.Where(t => t.Name == searchName).OrderBy(t => t.ID).FirstOrDefaultAsync();
116+
}
117+
}
69118
}

src/AnalyticsEngine/Common/Entities/LookupCaches/Discrete/KeywordCache.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@
44

55
namespace Common.Entities.LookupCaches
66
{
7-
internal class KeywordCache : DBLookupCache<KeyWord>
7+
internal class KeywordCache : DBLookupCacheForEntityWithName<KeyWord>
88
{
99
public KeywordCache(AnalyticsEntitiesContext context) : base(context) { }
1010
public override DbSet<KeyWord> EntityStore => DB.KeyWords;
11-
12-
13-
public async override Task<KeyWord> Load(string searchKey)
14-
{
15-
return await DB.KeyWords.SingleOrDefaultAsync(t => t.Name == searchKey);
16-
}
1711
}
1812
}

src/AnalyticsEngine/Common/Entities/LookupCaches/Discrete/LanguageCache.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,9 @@
55
namespace Common.Entities.LookupCaches
66
{
77

8-
public class LanguageCache : DBLookupCache<Language>
8+
public class LanguageCache : DBLookupCacheForEntityWithName<Language>
99
{
1010
public LanguageCache(AnalyticsEntitiesContext context) : base(context) { }
1111
public override DbSet<Language> EntityStore => DB.Languages;
12-
13-
14-
public async override Task<Language> Load(string searchKey)
15-
{
16-
return await DB.Languages.SingleOrDefaultAsync(t => t.Name == searchKey);
17-
}
1812
}
1913
}
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
using System.Data.Entity;
2+
using System.Linq;
23
using System.Threading.Tasks;
34

45
namespace Common.Entities.LookupCaches
56
{
6-
public class LicenseTypeCache : DBLookupCache<LicenseType>
7+
public class LicenseTypeCache : DBLookupCacheForEntityWithName<LicenseType>
78
{
89
public LicenseTypeCache(AnalyticsEntitiesContext context) : base(context) { }
910

1011
public override DbSet<LicenseType> EntityStore => this.DB.LicenseTypes;
1112

12-
public async override Task<LicenseType> Load(string searchName)
13-
{
14-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchName);
15-
}
1613
}
1714
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
using System.Data.Entity;
2+
using System.Linq;
23
using System.Threading.Tasks;
34

45
namespace Common.Entities.LookupCaches
56
{
6-
public class OfficeLocationCache : DBLookupCache<UserOfficeLocation>
7+
public class OfficeLocationCache : DBLookupCacheForEntityWithName<UserOfficeLocation>
78
{
89
public OfficeLocationCache(AnalyticsEntitiesContext context) : base(context) { }
910

1011
public override DbSet<UserOfficeLocation> EntityStore => this.DB.UserOfficeLocations;
11-
12-
public async override Task<UserOfficeLocation> Load(string searchName)
13-
{
14-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchName);
15-
}
1612
}
1713
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
using System.Data.Entity;
2+
using System.Linq;
23
using System.Threading.Tasks;
34

45
namespace Common.Entities.LookupCaches
56
{
6-
public class StateOrProvinceCache : DBLookupCache<StateOrProvince>
7+
public class StateOrProvinceCache : DBLookupCacheForEntityWithName<StateOrProvince>
78
{
89
public StateOrProvinceCache(AnalyticsEntitiesContext context) : base(context) { }
910

1011
public override DbSet<StateOrProvince> EntityStore => this.DB.StateOrProvinces;
11-
12-
public async override Task<StateOrProvince> Load(string searchName)
13-
{
14-
return await EntityStore.SingleOrDefaultAsync(t => t.Name == searchName);
15-
}
1612
}
1713
}

0 commit comments

Comments
 (0)