-
Notifications
You must be signed in to change notification settings - Fork 4
/
DbContext.cs
355 lines (320 loc) · 12.2 KB
/
DbContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity.Validation;
using System.Linq;
using System.Threading.Tasks;
using Havit.Data.Entity.Conventions;
using Havit.Data.Entity.Model;
using ForeignKeyIndexConvention = Havit.Data.Entity.Conventions.ForeignKeyIndexConvention;
namespace Havit.Data.Entity
{
/// <summary>
/// DbContext. Konfiguruje standardní DbContext (vypíná ProxyCreation a LazyLoading, odebírá a přidává některé konvence).
/// </summary>
public abstract class DbContext : System.Data.Entity.DbContext, IDbContext
{
/// <summary>
/// Singleton pro použití v konstruktoru používající DbContextDefaultDatabase.
/// </summary>
public static DbContextDefaultDatabase DefaultDatabase { get; } = new DbContextDefaultDatabase();
/// <summary>
/// Registr akcí k provedení po uložení změn.
/// </summary>
private List<Action> afterSaveChangesActions;
/// <summary>
/// Zpřístupňuje Configuration.AutoDetectChangesEnabled.
/// </summary>
public bool AutoDetectChangesEnabled
{
get { return Configuration.AutoDetectChangesEnabled; }
set { Configuration.AutoDetectChangesEnabled = value; }
}
/// <summary>
/// Konstruktor.
/// Použije "name=DefaultConnectionString" jako 'nameOrConnectionString'.
/// </summary>
protected DbContext() : base("name=DefaultConnectionString")
{
Initialize();
}
/// <summary>
/// Konstruktor. Použije se výchozí pojmenování databáze a výchozí datbázový server (voláním bezparametrického bázového konstruktoru).
/// Určeno pro použití konstruktoru tam, kde je takový třeba:
/// <code>
/// public class MyDbContext : DbContext
/// {
/// public MyDbContext() : base(DefaultDatabase)
/// {
/// // NOOP
/// }
/// }
/// </code>
/// </summary>
#pragma warning disable S3253 // "base()" constructor calls should not be explicitly made // JK: Chci ho zde pro přehlednost!
protected DbContext(DbContextDefaultDatabase dbContextDefaultDatabase) : base()
#pragma warning restore S3253 // "base()" constructor calls should not be explicitly made
{
Initialize();
}
/// <summary>
/// Constructs a new context instance using the given string as the name or connection
/// string for the database to which a connection will be made. See the class remarks
/// for how this is used to create a connection.
/// </summary>
/// <param name="nameOrConnectionString">Either the database name or a connection string.</param>
protected DbContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
Initialize();
}
private void Initialize()
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
/// <summary>
/// Konfiguruje model - přidává a odebírá konvence.
/// </summary>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
EntityTypeConfiguration<DataSeedVersion> dataSeedVersionEntity = modelBuilder.Entity<DataSeedVersion>();
dataSeedVersionEntity.ToTable("__DataSeed");
dataSeedVersionEntity.HasKey(item => item.ProfileName);
dataSeedVersionEntity.Property(item => item.Version);
// EF standardně pojmenovává tabulky v databázi v množném čísle (anglicky).
// Chceme pojmenovat tabulky v jednotném čísle (a nemrvnit češtinu ala "Fakturas"),
// proto odebereme konvenci zajišťující pojmenování v množném čísle.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// EF má standardně dost divné chování při pojmenování cizích klíčů:
// A) Pokud mám vlastnost
// public Language Language { get; set; }
// pak se sloupec v databázi jmenuje Language_Id
// B) Pokud mám vlastnosti
// public Language Langugage { get; set; }
// public int LanguageId { get; set; }
// pak se sloupec v databázi jmenuje LanguageId.
// To nevyhovuje, protože nemáme svobodu doplnit vlastnost LanguageId bez změny datového schématu.
// Proto přidáváme konvenci, která toto řeší, jak na vazbách 1:N, tak na vztahových tabulkách M:N.
modelBuilder.Conventions.Add<ForeignKeyNamingConvention>();
// EF standardně pojmenovává sloupec primárního klíče (obvykle Id) stejně.
// Ze zvyku chceme mít pojmenovaný klíč jako "TabulkaId", proto přidáme konvenci, která toto řeší.
modelBuilder.Conventions.Add<PrefixTableNameToPrimaryKeyNamingConvention>();
// Podpora nastavení datového typu Date pomocí atributu.
modelBuilder.Conventions.Add<DataTypePropertyAttributeConvention>();
// Standardní convention pro cizí klíče nefunguje korektně, neumožňuje sloupec použít ve více indexech.
// Použijeme proto vlastní konvenci, která toto řeší (a řeší i pojmenování indexu).
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.ForeignKeyIndexConvention>();
modelBuilder.Conventions.Add<ForeignKeyIndexConvention>();
// Pro sloupce pojmenované "Symbol" automaticky vytvoří unikátní index.
modelBuilder.Conventions.Add<SymbolPropertyIndexConvention>();
// Pro lokalizační tabulky vytvoří unikátní index na sloupcích ParentId a LanguageId.
modelBuilder.Conventions.Add<LocalizationTableIndexConvention>();
}
/// <summary>
/// Saves all changes made in this context to the underlying database.
/// </summary>
public override int SaveChanges()
{
int result = ExecuteWithSaveChangesExceptionHandling(base.SaveChanges);
AfterSaveChanges();
return result;
}
/// <summary>
/// Asynchronously saves all changes made in this context to the underlying database.
/// </summary>
public override async Task<int> SaveChangesAsync()
{
int result = await ExecuteWithSaveChangesExceptionHandling<Task<int>>(base.SaveChangesAsync).ConfigureAwait(false);
AfterSaveChanges();
return result;
}
private T ExecuteWithSaveChangesExceptionHandling<T>(Func<T> protectedAction)
{
try
{
return protectedAction.Invoke();
}
catch (DbEntityValidationException dbEntityValidationException)
{
// DbEntityValidationException je ošlivě formátovaná, tak vytvoříme novou instanci
// (tím neměníme typ vyhazované výjimky), nastavíme jí hezčí Message,
// předáme EntityValidationErrors a pro všechny případe původní výjimku vložíme do inner exception.
throw new DbEntityValidationException(dbEntityValidationException.FormatErrorMessage(), dbEntityValidationException.EntityValidationErrors, dbEntityValidationException);
}
}
/// <summary>
/// Spuštěno po save changes.
/// Zajišťuje volání registrovaných after save changes akcí (viz RegisterAfterSaveChangesAction).
/// </summary>
protected internal virtual void AfterSaveChanges()
{
afterSaveChangesActions?.ForEach(item => item.Invoke());
afterSaveChangesActions = null;
}
/// <summary>
/// Registruje akci k provedení po save changes. Akce je provedena metodou AfterSaveChanges.
/// Při opakovaném volání SaveChanges není akce volána opakovaně.
/// </summary>
public void RegisterAfterSaveChangesAction(Action action)
{
if (afterSaveChangesActions == null)
{
afterSaveChangesActions = new List<Action>();
}
afterSaveChangesActions.Add(action);
}
/// <summary>
/// Vrátí objekt pro přímý přístup k databázi.
/// </summary>
IDbContextDatabase IDbContext.Database
{
get
{
if (dbContextDatabase == null)
{
dbContextDatabase = new DbContextDatabase(this);
}
return dbContextDatabase;
}
}
private IDbContextDatabase dbContextDatabase;
/// <summary>
/// Uloží evidované změny.
/// </summary>
void IDbContext.SaveChanges()
{
this.SaveChanges();
}
/// <summary>
/// Uloží evidované změny.
/// </summary>
Task IDbContext.SaveChangesAsync()
{
return this.SaveChangesAsync();
}
/// <summary>
/// Vrátí objekty v daném stavu.
/// </summary>
object[] IDbContext.GetObjectsInState(EntityState state)
{
return ExecuteWithoutAutoDetectChanges(() => ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager
.GetObjectStateEntries(state) // vrací pole abstraktních ObjectStateEntry, které má dva potomky (bohužel jsou internal)
.Where(item => item.Entity != null) // a protože je internal, odlišujeme EntityEntry odlišit od RelationshipEntry podle toho, zda má hodnotu ve vlastnosti Entity
.Select(item => item.Entity).ToArray());
}
/// <summary>
/// Vrací stav entity z change trackeru.
/// </summary>
EntityState IDbContext.GetEntityState<TEntity>(TEntity entity)
{
return ExecuteWithoutAutoDetectChanges(() => Entry(entity).State);
}
/// <summary>
/// Nastaví objekt do požadovaného stavu.
/// </summary>
void IDbContext.SetEntityState<TEntity>(TEntity entity, EntityState entityState)
{
ExecuteWithoutAutoDetectChanges(() =>
{
this.Entry(entity).State = entityState;
return (object)null;
});
}
/// <summary>
/// Vrací true, pokud je daná vlastnost na entitě načtena.
/// </summary>
bool IDbContext.IsEntityReferenceLoaded<TEntity>(TEntity entity, string propertyName)
{
return ExecuteWithoutAutoDetectChanges(() => Entry(entity).Reference(propertyName).IsLoaded);
}
/// <summary>
/// Vrací true, pokud je daná vlastnost (kolekce) na entitě načtena.
/// </summary>
bool IDbContext.IsEntityCollectionLoaded<TEntity>(TEntity entity, string propertyName)
{
return ExecuteWithoutAutoDetectChanges(() => Entry(entity).Collection(propertyName).IsLoaded);
}
/// <summary>
/// Nastaví informaci o tom, zda byla daná vlastnost dané entity načtena. Viz DbReferenceEntry.IsLoaded.
/// </summary>
void IDbContext.SetEntityReferenceLoaded<TEntity>(TEntity entity, string propertyName, bool loadedValue)
{
ExecuteWithoutAutoDetectChanges(() => Entry(entity).Reference(propertyName).IsLoaded = loadedValue);
}
/// <summary>
/// Nastaví informaci o tom, zda byla daná vlastnost dané entity načtena. Viz DbCollectionEntry.IsLoaded.
/// </summary>
void IDbContext.SetEntityCollectionLoaded<TEntity>(TEntity entity, string propertyName, bool loadedValue)
{
ExecuteWithoutAutoDetectChanges(() => Entry(entity).Collection(propertyName).IsLoaded = loadedValue);
}
/// <summary>
/// Volá DetectChanges na ChangeTrackeru.
/// </summary>
void IDbContext.DetectChanges()
{
this.ChangeTracker.DetectChanges();
}
/// <summary>
/// Provede akci s AutoDetectChangesEnabled nastaveným na false, přičemž je poté AutoDetectChangesEnabled nastaven na původní hodnotu.
/// </summary>
public TResult ExecuteWithoutAutoDetectChanges<TResult>(Func<TResult> action)
{
if (AutoDetectChangesEnabled)
{
try
{
AutoDetectChangesEnabled = false;
return action();
}
finally
{
AutoDetectChangesEnabled = true;
}
}
else
{
return action();
}
}
/// <summary>
/// Provede akci s Configuration.UseDatabaseNullSemantics nastaveným na true, přičemž je poté Configuration.UseDatabaseNullSemantics nastaven na původní hodnotu.
/// </summary>
public TResult ExecuteWithDatabaseNullSemantics<TResult>(Func<TResult> action)
{
if (!Configuration.UseDatabaseNullSemantics)
{
try
{
Configuration.UseDatabaseNullSemantics = true;
return action();
}
finally
{
Configuration.UseDatabaseNullSemantics = false;
}
}
else
{
return action();
}
}
/// <summary>
/// Pouze pro existenci přetížení konstruktoru DbContextu, díky kterému se použije bezparametrický konstruktor předka.
/// </summary>
public sealed class DbContextDefaultDatabase
{
internal DbContextDefaultDatabase()
{
}
}
}
}