@@ -140,6 +140,21 @@ public partial class SQLiteConnection : IDisposable
140
140
141
141
public string DatabasePath { get ; private set ; }
142
142
143
+ // Dictionary of synchronization objects.
144
+ //
145
+ // To prevent database disruption, a database file must be accessed *synchronously*.
146
+ // For the purpose we create synchronous objects for each database file and store in the
147
+ // static dictionary to share it among all connections.
148
+ // The key of the dictionary is database file path and its value is an object to be used
149
+ // by lock() statement.
150
+ //
151
+ // Use case:
152
+ // - database file lock is done implicitly and automatically.
153
+ // - To prepend deadlock, application may lock a database file explicity by either way:
154
+ // - RunInTransaction(Action) locks the database during the transaction (for insert/update)
155
+ // - RunInDatabaseLock(Action) similarly locks the database but no transaction (for query)
156
+ private static Dictionary < string , object > syncObjects = new Dictionary < string , object > ( ) ;
157
+
143
158
public bool TimeExecution { get ; set ; }
144
159
145
160
#region debug tracing
@@ -195,6 +210,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st
195
210
throw new ArgumentException ( "Must be specified" , "databasePath" ) ;
196
211
197
212
DatabasePath = databasePath ;
213
+ mayCreateSyncObject ( databasePath ) ;
198
214
199
215
#if NETFX_CORE
200
216
SQLite3 . SetDirectory ( /*temp directory type*/ 2 , Windows . Storage . ApplicationData . Current . TemporaryFolder . Path ) ;
@@ -231,6 +247,19 @@ static SQLiteConnection ()
231
247
}
232
248
}
233
249
250
+ void mayCreateSyncObject ( string databasePath )
251
+ {
252
+ if ( ! syncObjects . ContainsKey ( databasePath ) ) {
253
+ syncObjects [ databasePath ] = new object ( ) ;
254
+ }
255
+ }
256
+
257
+ /// <summary>
258
+ /// Gets the synchronous object, to be lock the database file for updating.
259
+ /// </summary>
260
+ /// <value>The sync object.</value>
261
+ public object SyncObject { get { return syncObjects [ DatabasePath ] ; } }
262
+
234
263
public void EnableLoadExtension ( int onoff )
235
264
{
236
265
SQLite3 . Result r = SQLite3 . EnableLoadExtension ( Handle , onoff ) ;
@@ -1048,15 +1077,30 @@ public void Commit ()
1048
1077
public void RunInTransaction ( Action action )
1049
1078
{
1050
1079
try {
1051
- var savePoint = SaveTransactionPoint ( ) ;
1052
- action ( ) ;
1053
- Release ( savePoint ) ;
1080
+ lock ( syncObjects [ DatabasePath ] ) {
1081
+ var savePoint = SaveTransactionPoint ( ) ;
1082
+ action ( ) ;
1083
+ Release ( savePoint ) ;
1084
+ }
1054
1085
} catch ( Exception ) {
1055
1086
Rollback ( ) ;
1056
1087
throw ;
1057
1088
}
1058
1089
}
1059
1090
1091
+ /// <summary>
1092
+ /// Executes <param name="action"> while blocking other threads to access the same database.
1093
+ /// </summary>
1094
+ /// <param name="action">
1095
+ /// The <see cref="Action"/> to perform within a lock.
1096
+ /// </param>
1097
+ public void RunInDatabaseLock ( Action action )
1098
+ {
1099
+ lock ( syncObjects [ DatabasePath ] ) {
1100
+ action ( ) ;
1101
+ }
1102
+ }
1103
+
1060
1104
/// <summary>
1061
1105
/// Inserts all specified objects.
1062
1106
/// </summary>
@@ -2000,9 +2044,12 @@ public int ExecuteNonQuery ()
2000
2044
}
2001
2045
2002
2046
var r = SQLite3 . Result . OK ;
2003
- var stmt = Prepare ( ) ;
2004
- r = SQLite3 . Step ( stmt ) ;
2005
- Finalize ( stmt ) ;
2047
+ lock ( _conn . SyncObject ) {
2048
+ var stmt = Prepare ( ) ;
2049
+ r = SQLite3 . Step ( stmt ) ;
2050
+ Finalize ( stmt ) ;
2051
+ }
2052
+
2006
2053
if ( r == SQLite3 . Result . Done ) {
2007
2054
int rowsAffected = SQLite3 . Changes ( _conn . Handle ) ;
2008
2055
return rowsAffected ;
@@ -2057,33 +2104,32 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
2057
2104
_conn . InvokeTrace ( "Executing Query: " + this ) ;
2058
2105
}
2059
2106
2060
- var stmt = Prepare ( ) ;
2061
- try
2062
- {
2063
- var cols = new TableMapping . Column [ SQLite3 . ColumnCount ( stmt ) ] ;
2107
+ lock ( _conn . SyncObject ) {
2108
+ var stmt = Prepare ( ) ;
2109
+ try {
2110
+ var cols = new TableMapping . Column [ SQLite3 . ColumnCount ( stmt ) ] ;
2064
2111
2065
- for ( int i = 0 ; i < cols . Length ; i ++ ) {
2066
- var name = SQLite3 . ColumnName16 ( stmt , i ) ;
2067
- cols [ i ] = map . FindColumn ( name ) ;
2068
- }
2069
-
2070
- while ( SQLite3 . Step ( stmt ) == SQLite3 . Result . Row ) {
2071
- var obj = Activator . CreateInstance ( map . MappedType ) ;
2072
2112
for ( int i = 0 ; i < cols . Length ; i ++ ) {
2073
- if ( cols [ i ] == null )
2074
- continue ;
2075
- var colType = SQLite3 . ColumnType ( stmt , i ) ;
2076
- var val = ReadCol ( stmt , i , colType , cols [ i ] . ColumnType ) ;
2077
- cols [ i ] . SetValue ( obj , val ) ;
2078
- }
2079
- OnInstanceCreated ( obj ) ;
2080
- yield return ( T ) obj ;
2113
+ var name = SQLite3 . ColumnName16 ( stmt , i ) ;
2114
+ cols [ i ] = map . FindColumn ( name ) ;
2115
+ }
2116
+
2117
+ while ( SQLite3 . Step ( stmt ) == SQLite3 . Result . Row ) {
2118
+ var obj = Activator . CreateInstance ( map . MappedType ) ;
2119
+ for ( int i = 0 ; i < cols . Length ; i ++ ) {
2120
+ if ( cols [ i ] == null )
2121
+ continue ;
2122
+ var colType = SQLite3 . ColumnType ( stmt , i ) ;
2123
+ var val = ReadCol ( stmt , i , colType , cols [ i ] . ColumnType ) ;
2124
+ cols [ i ] . SetValue ( obj , val ) ;
2125
+ }
2126
+ OnInstanceCreated ( obj ) ;
2127
+ yield return ( T ) obj ;
2128
+ }
2129
+ } finally {
2130
+ SQLite3 . Finalize ( stmt ) ;
2081
2131
}
2082
2132
}
2083
- finally
2084
- {
2085
- SQLite3 . Finalize ( stmt ) ;
2086
- }
2087
2133
}
2088
2134
2089
2135
public T ExecuteScalar < T > ( )
@@ -2094,26 +2140,25 @@ public T ExecuteScalar<T> ()
2094
2140
2095
2141
T val = default ( T ) ;
2096
2142
2097
- var stmt = Prepare ( ) ;
2143
+ lock ( _conn . SyncObject ) {
2144
+ var stmt = Prepare ( ) ;
2098
2145
2099
- try
2100
- {
2101
- var r = SQLite3 . Step ( stmt ) ;
2102
- if ( r == SQLite3 . Result . Row ) {
2103
- var colType = SQLite3 . ColumnType ( stmt , 0 ) ;
2104
- val = ( T ) ReadCol ( stmt , 0 , colType , typeof ( T ) ) ;
2105
- }
2106
- else if ( r == SQLite3 . Result . Done ) {
2107
- }
2108
- else
2109
- {
2110
- throw SQLiteException . New ( r , SQLite3 . GetErrmsg ( _conn . Handle ) ) ;
2111
- }
2112
- }
2113
- finally
2114
- {
2115
- Finalize ( stmt ) ;
2116
- }
2146
+ try {
2147
+ var r = SQLite3 . Step ( stmt ) ;
2148
+ if ( r == SQLite3 . Result . Row ) {
2149
+ var colType = SQLite3 . ColumnType ( stmt , 0 ) ;
2150
+ val = ( T ) ReadCol ( stmt , 0 , colType , typeof ( T ) ) ;
2151
+ }
2152
+ else if ( r == SQLite3 . Result . Done ) {
2153
+ }
2154
+ else
2155
+ {
2156
+ throw SQLiteException . New ( r , SQLite3 . GetErrmsg ( _conn . Handle ) ) ;
2157
+ }
2158
+ } finally {
2159
+ Finalize ( stmt ) ;
2160
+ }
2161
+ }
2117
2162
2118
2163
return val ;
2119
2164
}
0 commit comments