1: /// <summary>
2: /// Simple entity repository
3: /// </summary>
4: /// <typeparam name="BaseType">Base class type of entity being managed by the repository</typeparam>
5: [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Makes sense in this circumstance.")]
6: public class Repository<BaseType> : Repository<BaseType, BaseType>
7: where BaseType : class, IObjectWithChangeTracker
8: {
9: /// <summary>
10: /// Initializes a new instance of the <see cref="Repository<BaseType>"/> class.
11: /// </summary>
12: /// <param name="contextFactory">The context factory.</param>
13: /// <param name="transactionManager">The transaction manager.</param>
14: public Repository(IContextFactory contextFactory, ITransactionManager transactionManager)
15: : base(contextFactory, transactionManager)
16: {
17: }
18: }
19:
20: /// <summary>Base class for entity repositories
21: /// </summary>
22: /// <typeparam name="BaseType">Base class type of entity being managed by the repository</typeparam>
23: /// <typeparam name="ConcreteType">Concrete (derived) class type of entity being managed by the repository</typeparam>
24: public class Repository<BaseType, ConcreteType> : IRepository<ConcreteType>, IDisposable
25: where BaseType : class, IObjectWithChangeTracker
26: where ConcreteType : class, BaseType
27: {
28: private ITransactionManager transactionManager;
29: private ObjectContext localContext;
30: private bool disposed;
31:
32: // hide the context from descendants, they should not be able to get to a context directly if we can help it. They can get to it through the ObjectSet which
33: // we can't hide, but make it difficult to keep the repository single focused.
34: private IContextFactory contextFactory;
35:
36: /// <summary>
37: /// Initializes a new instance of the <see cref="Repository{BaseType,ConcreteType}"/> class.
dding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"> 38: /// </summary>
39: /// <param name="contextFactory">Factory that generates ObjectContext objects.</param>
40: /// <param name="transactionManager">The transaction manager.</param>
41: public Repository(IContextFactory contextFactory, ITransactionManager transactionManager)
42: {
43: this.transactionManager = transactionManager;
44: this.contextFactory = contextFactory;
45: }
46:
47: /// <summary>
48: /// Gets the object set to act on given the current transaction state
49: /// </summary>
50: /// <value>The current object set.</value>
51: /// <returns>The ObjectSet to use for this repository</returns>
52: protected ObjectSet<BaseType> CurrentObjectSet
53: {
54: get
55: {
56: // if currently in a transaction, then assume it's context, otherwise use a locally created one.
57: ObjectContext context = TransactionManager != null ? TransactionManager.CurrentContext : null;
58:
59: // create a context and manage its life if we are not already under a transaction scope
60: if (context == null)
61: {
62: if (localContext == null)
63: {
64: localContext = contextFactory.CreateContext();
65: }
66:
67: context = localContext;
68: }
69:
70: return ((SrsModelContainer)context).GetObjectSet<BaseType>();
71: }
72: }
73:
74: /// <summary>
75: /// Gets the transaction manager.
76: /// </summary>
77: /// <value>The transaction manager.</value>
78: private ITransactionManager TransactionManager
79: {
80: get
81: {
82: return transactionManager;
83: }
84: }
85:
86: /// <summary>
87: /// Gets the first element in source that passes the test in predicate.
88: /// </summary>
89: /// <param name="predicate">The predicate.</param>
90: /// <param name="includes">The includes.</param>
91: /// <returns>
92: /// The first element in source that passes the test in predicate.
93: /// </returns>
94: public ConcreteType First(Expression<Func<ConcreteType, bool>> predicate, params string[] includes)
95: {
96: return GetObjectSet(includes).First(predicate);
97: }
98:
99: /// <summary>
100: /// Returns the first element of a sequence, or a default value if the sequence
101: /// contains no elements
102: /// </summary>
103: /// <param name="predicate">The predicate.</param>
104: /// <param name="includes">The includes.</param>
105: /// <returns>Returns the first element of a sequence, or a default value if the sequence
106: /// contains no elements.</returns>
107: public ConcreteType FirstOrDefault(Expression<Func<ConcreteType, bool>> predicate, params string[] includes)
108: {
109: return GetObjectSet(includes).FirstOrDefault(predicate);
110: }
111:
112: /// <summary>
113: /// The single element of the input sequence
114: /// </summary>
115: /// <param name="predicate">The predicate.</param>
116: /// <param name="includes">The includes.</param>
117: /// <returns>The single element of the input sequence.</returns>
118: public ConcreteType Single(Expression<Func<ConcreteType, bool>> predicate, params string[] includes)
119: {
120: return GetObjectSet(includes).Single(predicate);
121: }
122:
123: /// <summary>
124: /// Gets a collection of all persisted items that pass the test in the predicate
125: /// </summary>
126: /// <param name="predicate">The predicate.</param>
127: /// <param name="includes">The includes.</param>
128: /// <returns>Collection of all persisted items that pass the test in the predicate</returns>
129: public IList<ConcreteType> Find(Expression<Func<ConcreteType, bool>> predicate, params string[] includes)
130: {
131: return GetObjectSet(includes).Where(predicate).ToList();
132: }
133:
134: /// <summary>
135: /// Gets a collection of all persisted items
136: /// </summary>
137: /// <param name="includes">Sub-entities to include.</param>
138: /// <returns>Collection of all persisted items</returns>
139: public virtual IList<ConcreteType> FindAll(params string[] includes)
140: {
141: return GetObjectSet(includes).ToList();
142: }
143:
144: /// <summary>
145: /// Applies the changes for a single entity to the context
146: /// </summary>
147: /// <param name="toSave">Modified entity to apply changes for.</param>
148: public virtual void ApplyChanges(ConcreteType toSave)
149: {
150: ValidateAggregate(toSave);
151:
152: ApplyToContext(toSave);
153:
154: // if no current transaction, then save, otherwise it belongs to the unit of work scope
155: if (TransactionManager == null || TransactionManager.CurrentContext == null)
156: {
157: GetObjectSet().Context.SaveChanges();
158: }
159: }
160:
161: /// <summary> Applies the changes for a collection of entities to the context
162: /// </summary>
163: /// <param name="toSave">Collection of modified entities to apply changes for.</param>
164: public virtual void ApplyChanges(IEnumerable<ConcreteType> toSave)
165: {
166: foreach (var item in toSave)
167: {
168: ValidateAggregate(item);
169:
170: ApplyToContext(item);
171: }
172:
173: // if no current transaction, then save, otherwise it belongs to the unit of work scope
174: if (TransactionManager == null || TransactionManager.CurrentContext == null)
175: {
176: GetObjectSet().Context.SaveChanges();
177: }
178: }
179:
180: /// <summary>
181: /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
182: /// </summary>
183: public void Dispose()
184: {
185: this.Dispose(true);
186: GC.SuppressFinalize(this);
187: }
188:
189: /// <summary>
190: /// Refreshes the specified entities from the data store.
191: /// </summary>
192: /// <param name="entities">The entities.</param>
193: public void Refresh(params ConcreteType[] entities)
194: {
195: CurrentObjectSet.Context.Refresh(RefreshMode.StoreWins, entities);
196: }
197:
198: /// <summary>
199: /// Validates the specified record to save and throws ValidationException if condition is not met
200: /// </summary>
201: /// <param name="toSave">The record to save.</param>
202: protected virtual void ValidateAggregate(ConcreteType toSave)
203: {
204: // no default implementation
205: }
206:
207: /// <summary>
208: /// Gets the object set.
209: /// </summary>
210: /// <param name="includes">The includes.</param>
211: /// <returns>
212: /// Object set with the specified dependent objects included.
213: /// </returns>
214: protected ObjectQuery<ConcreteType> GetObjectSet(params string[] includes)
215: {
216: var objectSet = CurrentObjectSet.OfType<ConcreteType>();
217: foreach (var include in includes)
218: {
219: objectSet = objectSet.Include(include);
220: }
221:
222: return objectSet;
223: }
224:
225: /// <summary>
226: /// Applies changes for the given entity to context.
227: /// </summary>
228: /// <param name="entityToSave">The entity to save.</param>
229: protected void ApplyToContext(ConcreteType entityToSave)
230: {
231: // If the object toSave was an existing record loaded from the same context that we are attempting to save it back to
232: // then some special considerations are needed.
233: ObjectStateEntry entityStateEntry;
234: if (CurrentObjectSet.Context.ObjectStateManager.TryGetObjectStateEntry(entityToSave, out entityStateEntry))
235: {
236: object contextEntity = entityStateEntry.Entity;
237:
238: if (entityToSave.ChangeTracker.State == ObjectState.Deleted)
239: {
240: // it's possible to have a different reference to the same entity (entity keys are the same). This will cause an error
241: // when trying to delete, so we need to make sure that we are deleting the entity that is already in the store.
242: CurrentObjectSet.Context.ObjectStateManager.ChangeObjectState(contextEntity, EntityState.Deleted);
243: }
244: else if (entityToSave.ChangeTracker.State == ObjectState.Modified)
245: {
246: // if the entity already in the store is not the same reference as the entity we are trying to save (even though they represent the same record in the store)
247: // we need to move the changes over to the record already in the store. If they are the same, the changes are already there.
248: if (!object.ReferenceEquals(contextEntity, entityToSave))
249: {
250: CurrentObjectSet.Context.ApplyCurrentValues(CurrentObjectSet.EntitySet.Name, entityToSave);
251: }
252: }
253: }
254: else
255: {
256: // Apply changes only works when the object toSave does not already have a reference in the context's ObjectStateManager
257: CurrentObjectSet.ApplyChanges(entityToSave);
258: }
259: }
260:
261: /// <summary>
262: /// Releases unmanaged and - optionally - managed resources
263: /// </summary>
264: /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
265: private void Dispose(bool disposing)
266: {
267: if (disposed)
268: {
269: return;
270: }
271:
272: if (disposing)
273: {
274: if (localContext != null)
275: {
276: localContext.Dispose();
277: }
278: }
279:
280: disposed = true;
281: }
282: }