Skip to content

Commit 4d2559f

Browse files
committed
#16: Refactoring and added SqliteDropCreateDatabaseWhenModelChanges.
1 parent 280a3c5 commit 4d2559f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+464
-111
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace SQLite.CodeFirst.Console.Entity
4+
{
5+
public class CustomHistory : IHistory
6+
{
7+
public int Id { get; set; }
8+
public string Hash { get; set; }
9+
public string Context { get; set; }
10+
public DateTime CreateDate { get; set; }
11+
}
12+
}
Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Collections.Generic;
2-
using System.Data.Entity;
3-
using System.Data.Entity.ModelConfiguration.Conventions;
1+
using System.Data.Entity;
42
using SQLite.CodeFirst.Console.Entity;
53

64
namespace SQLite.CodeFirst.Console
@@ -16,8 +14,6 @@ public FootballDbContext()
1614

1715
protected override void OnModelCreating(DbModelBuilder modelBuilder)
1816
{
19-
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
20-
2117
ConfigureTeamEntity(modelBuilder);
2218
ConfigureStadionEntity(modelBuilder);
2319
ConfigureCoachEntity(modelBuilder);
@@ -61,51 +57,4 @@ private static void ConfigurePlayerEntity(DbModelBuilder modelBuilder)
6157
.WillCascadeOnDelete(true);
6258
}
6359
}
64-
65-
public class FootballDbInitializer : SqliteDropCreateDatabaseAlways<FootballDbContext>
66-
{
67-
public FootballDbInitializer(DbModelBuilder modelBuilder)
68-
: base(modelBuilder)
69-
{ }
70-
71-
protected override void Seed(FootballDbContext context)
72-
{
73-
context.Set<Team>().Add(new Team
74-
{
75-
Name = "YB",
76-
Coach = new Coach
77-
{
78-
City = "Zürich",
79-
FirstName = "Masssaman",
80-
LastName = "Nachn",
81-
Street = "Testingstreet 844"
82-
},
83-
Players = new List<Player>
84-
{
85-
new Player
86-
{
87-
City = "Bern",
88-
FirstName = "Marco",
89-
LastName = "Bürki",
90-
Street = "Wunderstrasse 43",
91-
Number = 12
92-
},
93-
new Player
94-
{
95-
City = "Berlin",
96-
FirstName = "Alain",
97-
LastName = "Rochat",
98-
Street = "Wonderstreet 13",
99-
Number = 14
100-
}
101-
},
102-
Stadion = new Stadion
103-
{
104-
Name = "Stade de Suisse",
105-
City = "Bern",
106-
Street = "Papiermühlestrasse 71"
107-
}
108-
});
109-
}
110-
}
11160
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using System.Data.Entity;
3+
using SQLite.CodeFirst.Console.Entity;
4+
5+
namespace SQLite.CodeFirst.Console
6+
{
7+
public class FootballDbInitializer : SqliteDropCreateDatabaseWhenModelChanges<FootballDbContext>
8+
{
9+
public FootballDbInitializer(DbModelBuilder modelBuilder)
10+
: base(modelBuilder, typeof(CustomHistory))
11+
{ }
12+
13+
protected override void Seed(FootballDbContext context)
14+
{
15+
context.Set<Team>().Add(new Team
16+
{
17+
Name = "YB",
18+
Coach = new Coach
19+
{
20+
City = "Zürich",
21+
FirstName = "Masssaman",
22+
LastName = "Nachn",
23+
Street = "Testingstreet 844"
24+
},
25+
Players = new List<Player>
26+
{
27+
new Player
28+
{
29+
City = "Bern",
30+
FirstName = "Marco",
31+
LastName = "Bürki",
32+
Street = "Wunderstrasse 43",
33+
Number = 12
34+
},
35+
new Player
36+
{
37+
City = "Berlin",
38+
FirstName = "Alain",
39+
LastName = "Rochat",
40+
Street = "Wonderstreet 13",
41+
Number = 14
42+
}
43+
},
44+
Stadion = new Stadion
45+
{
46+
Name = "Stade de Suisse",
47+
City = "Bern",
48+
Street = "Papiermühlestrasse 71"
49+
}
50+
});
51+
}
52+
}
53+
}

SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@
7777
<Link>Properties\AssemblyVersionInfo.cs</Link>
7878
</Compile>
7979
<Compile Include="Entity\Coach.cs" />
80+
<Compile Include="Entity\CustomHistory.cs" />
8081
<Compile Include="Entity\IEntity.cs" />
8182
<Compile Include="Entity\Person.cs" />
8283
<Compile Include="Entity\Player.cs" />
8384
<Compile Include="Entity\Stadion.cs" />
8485
<Compile Include="Entity\Team.cs" />
8586
<Compile Include="FootballDbContext.cs" />
87+
<Compile Include="FootballDbInitializer.cs" />
8688
<Compile Include="Program.cs" />
8789
<Compile Include="Properties\AssemblyInfo.cs" />
8890
</ItemGroup>

SQLite.CodeFirst/SqliteCreateDatabaseIfNotExists.cs renamed to SQLite.CodeFirst/DbInitializers/SqliteCreateDatabaseIfNotExists.cs

File renamed without changes.

SQLite.CodeFirst/SqliteDropCreateDatabaseAlways.cs renamed to SQLite.CodeFirst/DbInitializers/SqliteDropCreateDatabaseAlways.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public override void InitializeDatabase(TContext context)
3030
{
3131
string databseFilePath = GetDatabasePathFromContext(context);
3232

33-
bool dbExists = File.Exists(databseFilePath);
34-
if (dbExists)
33+
bool exists = File.Exists(databseFilePath);
34+
if (exists)
3535
{
3636
File.Delete(databseFilePath);
3737
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using System;
2+
using System.Data.Common;
3+
using System.Data.Entity;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using SQLite.CodeFirst.Utility;
8+
9+
namespace SQLite.CodeFirst
10+
{
11+
/// <summary>
12+
/// An implementation of <see cref="IDatabaseInitializer{TContext}"/> that will always recreate and optionally re-seed the
13+
/// database the first time that a context is used in the app domain or if the model has changed.
14+
/// To seed the database, create a derived class and override the Seed method.
15+
/// <remarks>
16+
/// To detect model changes a new table (implementation of <see cref="IHistory"/>) is added to the database.
17+
/// There is one record in this table which holds the hash of the SQL-statement which was generated from the model
18+
/// executed to create the database. When initializing the database the initializer checks if the hash of the SQL-statement for the
19+
/// model is still the same as the hash in the database. If you use this initializer on a existing database, this initializer
20+
/// will interpret this as model change because of the new <see cref="IHistory"/> table.
21+
/// Notice that a database can be used by more than one context. Therefore the name of the context is saved as a part of the history record.
22+
/// </remarks>
23+
/// </summary>
24+
/// <typeparam name="TContext">The type of the context.</typeparam>
25+
public class SqliteDropCreateDatabaseWhenModelChanges<TContext> : SqliteInitializerBase<TContext>
26+
where TContext : DbContext
27+
{
28+
private readonly Type historyEntityType;
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="SqliteDropCreateDatabaseWhenModelChanges{TContext}"/> class.
32+
/// </summary>
33+
/// <param name="modelBuilder">The model builder.</param>
34+
public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder)
35+
: base(modelBuilder)
36+
{
37+
historyEntityType = typeof(History);
38+
ConfigureHistoryEntity();
39+
}
40+
41+
/// <summary>
42+
/// Initializes a new instance of the <see cref="SqliteDropCreateDatabaseWhenModelChanges{TContext}"/> class.
43+
/// </summary>
44+
/// <param name="modelBuilder">The model builder.</param>
45+
/// <param name="historyEntityType">Type of the history entity (must implement <see cref="IHistory"/> and provide an parameterless constructor).</param>
46+
public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder, Type historyEntityType)
47+
: base(modelBuilder)
48+
{
49+
this.historyEntityType = historyEntityType;
50+
ConfigureHistoryEntity();
51+
}
52+
53+
54+
protected void ConfigureHistoryEntity()
55+
{
56+
HistoryEntityTypeValidator.EnsureValidType(historyEntityType);
57+
ModelBuilder.RegisterEntityType(historyEntityType);
58+
}
59+
60+
/// <summary>
61+
/// Initialize the database for the given context.
62+
/// Generates the SQLite-DDL from the model and executs it against the database.
63+
/// After that the <see cref="Seed" /> method is executed.
64+
/// All actions are be executed in transactions.
65+
/// </summary>
66+
/// <param name="context">The context.</param>
67+
public override void InitializeDatabase(TContext context)
68+
{
69+
string databseFilePath = GetDatabasePathFromContext(context);
70+
71+
bool dbExists = File.Exists(databseFilePath);
72+
if (dbExists)
73+
{
74+
if (IsSameModel(context))
75+
{
76+
return;
77+
}
78+
79+
DeleteDatabase(context, databseFilePath);
80+
base.InitializeDatabase(context);
81+
SaveHistory(context);
82+
}
83+
else
84+
{
85+
base.InitializeDatabase(context);
86+
SaveHistory(context);
87+
}
88+
}
89+
90+
private void DeleteDatabase(TContext context, string databseFilePath)
91+
{
92+
context.Database.Connection.Close();
93+
GC.Collect();
94+
95+
// TODO: Comment
96+
for (int i = 0; i < 5; i++)
97+
{
98+
try
99+
{
100+
File.Delete(databseFilePath);
101+
// TODO: Throw exception if 5 tries..
102+
break;
103+
}
104+
catch (Exception)
105+
{
106+
System.Threading.Thread.Sleep(1);
107+
}
108+
}
109+
}
110+
111+
private void SaveHistory(TContext context)
112+
{
113+
var hash = GetHashFromModel(context.Database.Connection);
114+
var history = GetHistoryRecord(context);
115+
EntityState entityState;
116+
if (history == null)
117+
{
118+
history = (IHistory)Activator.CreateInstance(historyEntityType);
119+
entityState = EntityState.Added;
120+
}
121+
else
122+
{
123+
entityState = EntityState.Modified;
124+
}
125+
126+
history.Context = context.GetType().FullName;
127+
history.Hash = hash;
128+
history.CreateDate = DateTime.UtcNow;
129+
130+
context.Set(historyEntityType).Attach(history);
131+
context.Entry(history).State = entityState;
132+
context.SaveChanges();
133+
}
134+
135+
private bool IsSameModel(TContext context)
136+
{
137+
try
138+
{
139+
var hash = GetHashFromModel(context.Database.Connection);
140+
var history = GetHistoryRecord(context);
141+
return history?.Hash == hash;
142+
}
143+
catch (Exception)
144+
{
145+
return false;
146+
}
147+
}
148+
149+
private IHistory GetHistoryRecord(TContext context)
150+
{
151+
return context.Set(historyEntityType)
152+
.AsNoTracking()
153+
.ToListAsync()
154+
.Result
155+
.Cast<IHistory>()
156+
.SingleOrDefault();
157+
}
158+
159+
private string GetHashFromModel(DbConnection connection)
160+
{
161+
var sql = GetSqlFromModel(connection);
162+
string hash = HashCreator.CreateHash(sql);
163+
return hash;
164+
}
165+
166+
private string GetSqlFromModel(DbConnection connection)
167+
{
168+
var model = ModelBuilder.Build(connection);
169+
var sqliteSqlGenerator = new SqliteSqlGenerator(model.StoreModel);
170+
return sqliteSqlGenerator.Generate();
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)