Skip to content

Commit 39186d9

Browse files
author
Jeff Treuting
committed
DbContext duplicate key issue fixed
Added more advanced logic in the UpdateItem to add the current item to the current context if needed or to update the values of the item that is already in context. This seems to fix the duplicate issue error without having to change DynamicProxy classes to POCOs. If needed I have that logic available and it just needs to be tested more. Fixes #50 Added a MaxResults property and configuration value option to all CachingStrategies. If the number of results from a GetAll or FindAll is more than this number then the results are not cached. If it null (the default) then the check is not done and the results are always cached. Fixes #65
1 parent 7eec854 commit 39186d9

21 files changed

Lines changed: 192 additions & 62 deletions

SharpRepository.Ef5Repository/Ef5CompoundKeyRepositoryBase.cs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,29 @@ protected override void DeleteItem(T entity)
3838

3939
protected override void UpdateItem(T entity)
4040
{
41-
// mark this entity as modified, in case it is not currently attached to this context
42-
Context.Entry(entity).State = EntityState.Modified;
41+
var entry = Context.Entry<T>(entity);
42+
43+
if (entry.State == EntityState.Detached)
44+
{
45+
object[] keys;
46+
47+
if (GetPrimaryKeys(entity, out keys))
48+
{
49+
// check to see if this item is already attached
50+
// if it is then we need to copy the values to the attached value instead of changing the State to modified since it will throw a duplicate key exception
51+
// specifically: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
52+
var attachedEntity = Context.Set<T>().Find(keys);
53+
if (attachedEntity != null)
54+
{
55+
Context.Entry(attachedEntity).CurrentValues.SetValues(entity);
56+
57+
return;
58+
}
59+
}
60+
}
61+
62+
// default
63+
entry.State = EntityState.Modified;
4364
}
4465

4566
protected override void SaveChanges()
@@ -104,8 +125,30 @@ protected override void DeleteItem(T entity)
104125

105126
protected override void UpdateItem(T entity)
106127
{
107-
// mark this entity as modified, in case it is not currently attached to this context
108-
Context.Entry(entity).State = EntityState.Modified;
128+
var entry = Context.Entry<T>(entity);
129+
130+
if (entry.State == EntityState.Detached)
131+
{
132+
TKey key;
133+
TKey2 key2;
134+
135+
if (GetPrimaryKey(entity, out key, out key2))
136+
{
137+
// check to see if this item is already attached
138+
// if it is then we need to copy the values to the attached value instead of changing the State to modified since it will throw a duplicate key exception
139+
// specifically: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
140+
var attachedEntity = Context.Set<T>().Find(key, key2);
141+
if (attachedEntity != null)
142+
{
143+
Context.Entry(attachedEntity).CurrentValues.SetValues(entity);
144+
145+
return;
146+
}
147+
}
148+
}
149+
150+
// default
151+
entry.State = EntityState.Modified;
109152
}
110153

111154
protected override void SaveChanges()
@@ -170,8 +213,31 @@ protected override void DeleteItem(T entity)
170213

171214
protected override void UpdateItem(T entity)
172215
{
173-
// mark this entity as modified, in case it is not currently attached to this context
174-
Context.Entry(entity).State = EntityState.Modified;
216+
var entry = Context.Entry<T>(entity);
217+
218+
if (entry.State == EntityState.Detached)
219+
{
220+
TKey key;
221+
TKey2 key2;
222+
TKey3 key3;
223+
224+
if (GetPrimaryKey(entity, out key, out key2, out key3))
225+
{
226+
// check to see if this item is already attached
227+
// if it is then we need to copy the values to the attached value instead of changing the State to modified since it will throw a duplicate key exception
228+
// specifically: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
229+
var attachedEntity = Context.Set<T>().Find(key, key2, key3);
230+
if (attachedEntity != null)
231+
{
232+
Context.Entry(attachedEntity).CurrentValues.SetValues(entity);
233+
234+
return;
235+
}
236+
}
237+
}
238+
239+
// default
240+
entry.State = EntityState.Modified;
175241
}
176242

177243
protected override void SaveChanges()

SharpRepository.Ef5Repository/Ef5RepositoryBase.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ private void Initialize(DbContext dbContext, bool hasCaching)
2525
{
2626
Context = dbContext;
2727
DbSet = Context.Set<T>();
28-
29-
// this could solve issue #50 where DynamicProxy objects mess up the cache
30-
// if (hasCaching)
31-
// {
32-
// Context.Configuration.ProxyCreationEnabled = false;
33-
// }
3428
}
3529

3630
protected override void AddItem(T entity)
@@ -54,8 +48,29 @@ protected override void DeleteItem(T entity)
5448

5549
protected override void UpdateItem(T entity)
5650
{
57-
// mark this entity as modified, in case it is not currently attached to this context
58-
Context.Entry(entity).State = EntityState.Modified;
51+
var entry = Context.Entry<T>(entity);
52+
53+
if (entry.State == EntityState.Detached)
54+
{
55+
TKey key;
56+
57+
if (GetPrimaryKey(entity, out key))
58+
{
59+
// check to see if this item is already attached
60+
// if it is then we need to copy the values to the attached value instead of changing the State to modified since it will throw a duplicate key exception
61+
// specifically: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
62+
var attachedEntity = Context.Set<T>().Find(key);
63+
if (attachedEntity != null)
64+
{
65+
Context.Entry(attachedEntity).CurrentValues.SetValues(entity);
66+
67+
return;
68+
}
69+
}
70+
}
71+
72+
// default
73+
entry.State = EntityState.Modified;
5974
}
6075

6176
protected override void SaveChanges()

SharpRepository.Repository/Caching/CachingStrategyBase.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ public abstract class CachingStrategyBase<T, TKey> : ICachingStrategy<T, TKey>
1111
private ICachingProvider _cachingProvider;
1212
public string CachePrefix { get; set; }
1313
protected string TypeFullName { get; set; }
14+
public int? MaxResults { get; set; }
1415

1516
internal CachingStrategyBase()
1617
{
1718
}
1819

19-
internal CachingStrategyBase(ICachingProvider cachingProvider)
20+
internal CachingStrategyBase(int? maxResults, ICachingProvider cachingProvider)
2021
{
2122
CachePrefix = "#Repo";
2223
CachingProvider = cachingProvider;
24+
MaxResults = maxResults;
2325

2426
TypeFullName = typeof(T).FullName ?? typeof(T).Name; // sometimes FullName returns null in certain derived type situations, so I added the check to use the Name property if FullName is null
2527
}

SharpRepository.Repository/Caching/CompoundKeyCachingStrategyBase.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ internal CompoundKeyCachingStrategyBase()
99
{
1010
}
1111

12-
internal CompoundKeyCachingStrategyBase(ICachingProvider cachingProvider)
13-
: base(cachingProvider)
12+
internal CompoundKeyCachingStrategyBase(int? maxResults, ICachingProvider cachingProvider)
13+
: base(maxResults, cachingProvider)
1414
{
1515
}
1616

@@ -37,8 +37,8 @@ internal CompoundKeyCachingStrategyBase()
3737
{
3838
}
3939

40-
internal CompoundKeyCachingStrategyBase(ICachingProvider cachingProvider)
41-
: base(cachingProvider)
40+
internal CompoundKeyCachingStrategyBase(int? maxResults, ICachingProvider cachingProvider)
41+
: base(maxResults, cachingProvider)
4242
{
4343
}
4444

@@ -70,8 +70,8 @@ internal CompoundKeyCachingStrategyBase()
7070
{
7171
}
7272

73-
internal CompoundKeyCachingStrategyBase(ICachingProvider cachingProvider)
74-
: base(cachingProvider)
73+
internal CompoundKeyCachingStrategyBase(int? maxResults, ICachingProvider cachingProvider)
74+
: base(maxResults, cachingProvider)
7575
{
7676
}
7777

SharpRepository.Repository/Caching/CompoundKeyCachingStrategyCommon.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public abstract class CompoundKeyCachingStrategyCommon<T> where T : class
1111
private ICachingProvider _cachingProvider;
1212
public string CachePrefix { get; set; }
1313
protected string TypeFullName { get; set; }
14+
public int? MaxResults { get; set; }
1415

1516
public ICachingProvider CachingProvider
1617
{
@@ -20,10 +21,11 @@ public ICachingProvider CachingProvider
2021

2122
internal CompoundKeyCachingStrategyCommon() { }
2223

23-
internal CompoundKeyCachingStrategyCommon(ICachingProvider cachingProvider)
24+
internal CompoundKeyCachingStrategyCommon(int? maxResults, ICachingProvider cachingProvider)
2425
{
2526
CachePrefix = "#Repo";
2627
CachingProvider = cachingProvider;
28+
MaxResults = maxResults;
2729

2830
TypeFullName = typeof(T).FullName ?? typeof(T).Name; // sometimes FullName returns null in certain derived type situations, so I added the check to use the Name property if FullName is null
2931
}

SharpRepository.Repository/Caching/StandardCachingStrategy.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public StandardCachingStrategy()
2323
/// </summary>
2424
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
2525
public StandardCachingStrategy(ICachingProvider cachingProvider)
26-
: base(cachingProvider)
26+
: base(null, cachingProvider)
2727
{
2828
Partition = null;
2929
}
@@ -49,7 +49,7 @@ public StandardCachingStrategy() : base()
4949
/// </summary>
5050
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
5151
public StandardCachingStrategy(ICachingProvider cachingProvider)
52-
: base(cachingProvider)
52+
: base(null, cachingProvider)
5353
{
5454
Partition = null;
5555
}
@@ -77,7 +77,7 @@ public StandardCachingStrategy()
7777
/// </summary>
7878
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
7979
public StandardCachingStrategy(ICachingProvider cachingProvider)
80-
: base(cachingProvider)
80+
: base(null, cachingProvider)
8181
{
8282
Partition = null;
8383
}

SharpRepository.Repository/Caching/StandardCachingStrategyBase.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Linq.Expressions;
45
using System.Runtime.Caching;
56
using SharpRepository.Repository.Helpers;
@@ -21,12 +22,12 @@ public abstract class StandardCachingStrategyBase<T, TKey, TPartition> : Caching
2122
public Expression<Func<T, TPartition>> Partition { get; set; }
2223

2324
internal StandardCachingStrategyBase()
24-
: this(new InMemoryCachingProvider())
25+
: this(null, new InMemoryCachingProvider())
2526
{
2627
}
2728

28-
internal StandardCachingStrategyBase(ICachingProvider cachingProvider)
29-
: base(cachingProvider)
29+
internal StandardCachingStrategyBase(int? maxResults,ICachingProvider cachingProvider)
30+
: base(maxResults, cachingProvider)
3031
{
3132
WriteThroughCachingEnabled = true;
3233
GenerationalCachingEnabled = true;
@@ -62,6 +63,7 @@ public override bool TryGetAllResult<TResult>(IQueryOptions<T> queryOptions, Exp
6263
public override void SaveGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
6364
{
6465
if (!GenerationalCachingEnabled) return;
66+
if (MaxResults.HasValue && result.Count() > MaxResults.Value) return;
6567

6668
base.SaveGetAllResult(queryOptions, selector, result);
6769
}
@@ -78,6 +80,7 @@ public override bool TryFindAllResult<TResult>(ISpecification<T> criteria, IQuer
7880
public override void SaveFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
7981
{
8082
if (!GenerationalCachingEnabled) return;
83+
if (MaxResults.HasValue && result.Count() > MaxResults.Value) return;
8184

8285
base.SaveFindAllResult(criteria, queryOptions, selector, result);
8386
}

SharpRepository.Repository/Caching/StandardCachingStrategyConfiguration.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ public StandardCachingStrategyConfiguration(string name) : this(name, true, true
99
}
1010

1111
public StandardCachingStrategyConfiguration(string name, bool writeThroughCachingEnabled, bool generationalCachingEnabled)
12+
: this(name, writeThroughCachingEnabled, generationalCachingEnabled, null)
13+
{
14+
}
15+
16+
public StandardCachingStrategyConfiguration(string name, bool writeThroughCachingEnabled, bool generationalCachingEnabled, int? maxResults)
1217
{
1318
Name = name;
1419
WriteThroughCachingEnabled = writeThroughCachingEnabled;
1520
GeneraltionalCachingEnabled = generationalCachingEnabled;
16-
Factory = typeof (StandardConfigCachingStrategyFactory);
21+
MaxResults = maxResults;
22+
Factory = typeof(StandardConfigCachingStrategyFactory);
1723
}
1824

1925
public bool WriteThroughCachingEnabled

SharpRepository.Repository/Caching/StandardCachingStrategyWithPartition.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class StandardCachingStrategyWithPartition<T> : StandardCachingStrategyBa
1515
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
1616
/// <param name="partition">The property that should be used for partitioning.</param>
1717
public StandardCachingStrategyWithPartition(ICachingProvider cachingProvider, Expression<Func<T, int>> partition)
18-
: base(cachingProvider)
18+
: base(null, cachingProvider)
1919
{
2020
Partition = partition;
2121
}
@@ -43,7 +43,7 @@ public class StandardCachingStrategyWithPartition<T, TKey> : StandardCachingStra
4343
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
4444
/// <param name="partition">The property that should be used for partitioning.</param>
4545
public StandardCachingStrategyWithPartition(ICachingProvider cachingProvider, Expression<Func<T, int>> partition)
46-
: base(cachingProvider)
46+
: base(null, cachingProvider)
4747
{
4848
Partition = partition;
4949
}
@@ -81,7 +81,7 @@ public StandardCachingStrategyWithPartition(Expression<Func<T, TPartition>> part
8181
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
8282
/// <param name="partition">The property that should be used for partitioning.</param>
8383
public StandardCachingStrategyWithPartition(ICachingProvider cachingProvider, Expression<Func<T, TPartition>> partition)
84-
: base(cachingProvider)
84+
: base(null, cachingProvider)
8585
{
8686
Partition = partition;
8787
}
@@ -111,7 +111,7 @@ public StandardCachingStrategyWithPartition(Expression<Func<T, TPartition>> part
111111
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
112112
/// <param name="partition">The property that should be used for partitioning.</param>
113113
public StandardCachingStrategyWithPartition(ICachingProvider cachingProvider, Expression<Func<T, TPartition>> partition)
114-
: base(cachingProvider)
114+
: base(null, cachingProvider)
115115
{
116116
Partition = partition;
117117
}

SharpRepository.Repository/Caching/StandardCompoundKeyCachingStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public StandardCompoundKeyCachingStrategy()
2121
/// </summary>
2222
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
2323
public StandardCompoundKeyCachingStrategy(ICachingProvider cachingProvider)
24-
: base(cachingProvider)
24+
: base(null, cachingProvider)
2525
{
2626
Partition = null;
2727
}

0 commit comments

Comments
 (0)