csharp: LocalDataCache.sync

app.config:

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
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="GBADesktopClient.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <connectionStrings>
        <add name="GBADesktopClient.Properties.Settings.ServerGBAppraiseDemoConnectionString"
            connectionString="Data Source=.;Initial Catalog=GBAppraiseDemo;Persist Security Info=True;User ID=gbauser;Password=gbauser"
            providerName="System.Data.SqlClient" />
        <add name="GBADesktopClient.Properties.Settings.ClientGBAppraiseDemoConnectionString"
            connectionString="Data Source=|DataDirectory|\GBAppraiseDemo.sdf;Max Database Size=2047"
            providerName="Microsoft.SqlServerCe.Client.3.5" />
    </connectionStrings>
    <applicationSettings>
        <GBADesktopClient.Properties.Settings>
            <setting name="SyncWebServiceURL" serializeAs="String">
                <value>http://yourserver/Service.asmx</value>
            </setting>
        </GBADesktopClient.Properties.Settings>
    </applicationSettings>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IGBACacheSyncContract" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
                    allowCookies="false">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <reliableSession ordered="true" inactivityTimeout="00:10:00"
                        enabled="false" />
                    <security mode="Message">
                        <transport clientCredentialType="Windows" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="Windows" negotiateServiceCredential="true"
                            algorithmSuite="Default" establishSecurityContext="true" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/GBACacheSyncService/"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IGBACacheSyncContract"
                contract="GBAConfiguredSyncWcfService.IGBACacheSyncContract"
                name="WSHttpBinding_IGBACacheSyncContract">
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

  

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
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServerCe;
using Microsoft.Synchronization;
 
 
namespace GBADeviceClient.Sync
{
    public class ClientSyncAgent : SyncAgent
    {
 
        public ClientSyncAgent()
        {
            //Hook between SyncAgent and SqlCeClientSyncProvider
            this.LocalProvider = new SqlCeClientSyncProvider(Settings.Default.LocalConnectionString, true);
             
            //Adds the JobList and PropertyDetails tables to the SyncAgent
            //setting the SyncDirection to bidirectional
            //drop and recreate the table if exists
            this.Configuration.SyncTables.Add("JobList");
            this.Configuration.SyncTables.Add("PropertyDetails");
            this.Configuration.SyncTables["JobList"].SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables["JobList"].CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            this.Configuration.SyncTables["PropertyDetails"].SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables["PropertyDetails"].CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
 
             
            // The ServerSyncProviderProxy is a type used to abstract the particular transport
            // It simply uses reflection to map known method names required by the SyncProvider
            // In this case, we hand edited a Web Service proxy
            // The web service proxy required editing as VS generates proxies for all types returned by a web servcie
            // In this case, we have all the types for Sync Services, and duplicating types will cause errors
            this.RemoteProvider =
                new ServerSyncProviderProxy(
                    new Sync.ConfiguredSyncWebServiceProxy(Settings.Default.WebServiceURL));
        }
    }
}

  

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
using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data.SqlServerCe;
 
namespace GBADeviceClient
{
 
    /// <summary>
    /// https://www.microsoft.com/zh-cn/download/details.aspx?id=15784  Microsoft Synchronization Services for ADO.NET - 简体中文
    /// https://www.microsoft.com/zh-CN/download/details.aspx?id=6497  Microsoft SQL Server Compact 3.5 联机丛书和示例
    /// System.Data.SqlServerCe
    /// C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Devices
    /// 如何:将本地数据库和远程数据库配置为双向同步
    /// https://docs.microsoft.com/zh-cn/previous-versions/bb629326%28v%3dvs.110%29
    /// https://www.codeproject.com/Articles/22122/Database-local-cache
    /// https://docs.microsoft.com/zh-cn/previous-versions/aa983341%28v%3dvs.110%29 SQL Server Compact 4.0 和 Visual Studio
    /// https://www.microsoft.com/en-us/download/details.aspx?id=21880 Microsoft SQL Server Compact 4.0 Books Online
    /// </summary>
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [MTAThread]
        static void Main()
        {
            //Validate the database exists
            // If the local database doesn't exist, the app requires initilization
            using (SqlCeConnection conn = new SqlCeConnection(Settings.Default.LocalConnectionString))
            {
                if (!System.IO.File.Exists(conn.Database))
                {
                    DialogResult result = MessageBox.Show(
                        "The application requires a first time sync to continue.  Would you like to Sync Now?",
                        "Fist Time Run",
                        MessageBoxButtons.OKCancel,
                        MessageBoxIcon.Exclamation,
                        MessageBoxDefaultButton.Button1);
                    if (result == DialogResult.OK)
                    {
                        try
                        {
                            using (SynchronizingProgress progressForm = new SynchronizingProgress())
                            {
                                // Pop a Progress form to get the cursor and provide feedback
                                // on what's happening
                                // The current UI is simply to make sure the wiat cursor shows
                                progressForm.Show();
                                // Make sure the form is displayed
                                Application.DoEvents();
                                Cursor.Current = Cursors.WaitCursor;
                                Cursor.Show();
                                Sync.ClientSyncAgent syncAgent = new Sync.ClientSyncAgent();
                                syncAgent.Synchronize();
                            }
                        }
                        catch (Exception ex)
                        {
                            // Oooops, something happened
                            MessageBox.Show(
                                "Unable to synchronize..." + Environment.NewLine + ex.ToString(),
                                "Error during initial sync",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Exclamation,
                                MessageBoxDefaultButton.Button1);
                        }
                        finally
                        {
                            //Always, always, be sure to reset the cursor
                            Cursor.Current = Cursors.Default;
                        }
                    }
                    else
                        return;
                } // If database exists
            } // Using conn
             
            // Good to go
            Application.Run(new GBAppraiseUI());
        }
    }
}

  

https://www.codeproject.com/Articles/22122/Database-local-cache

 

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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Data.Common;
 
namespace Konamiman.Data
{
    /// <summary>
    /// Represents a local filesystem based cache for binary objects stored in a database https://www.codeproject.com/Articles/22122/Database-local-cache
    /// </summary>
    /// <remarks>
    /// <para>
    /// This class allows you to store binary objects in a database table, but using the a local filesystem cache
    /// to increase the data retrieval speed when requesting the same data repeatedly.
    /// </para>
    /// <para>
    /// To use the class, you need a table with three columns: a string column for the object name
    /// (objects are uniquely identified by their names), a binary column
    /// for the object value, and a timestamp column (any column type is ok as long as the column value automatically changes
    /// when the value column changes). You need also a directory in the local filesystem. You specify these values
    /// in the class constructor, or via class properties.
    /// </para>
    /// <para>
    /// When you first request an object, it is retrieved from the database and stored in the local cache.
    /// The next time you request the same object, the timestamps of the cached object and the database object
    /// are compared. If they match, the cached file is returned directly. Otherwise, the cached file is updated
    /// with the current object value from the database.
    /// </para>
    /// </remarks>
    class DatabaseFileCache
    {
        #region Fields and properties
 
        //SQL commands used for database access
        SqlCommand selectValueCommand;
        SqlCommand selectTimestampCommand;
        SqlCommand fileExistsCommand;
        SqlCommand insertCommand;
        SqlCommand getNamesCommand;
        SqlCommand deleteCommand;
        SqlCommand renameCommand;
 
        //The local cache directory
        DirectoryInfo cacheDirectory;
 
        /// <summary>
        /// Gets or sets the maximum execution time for SQL commands, in seconds.
        /// </summary>
        /// <remarks>
        /// Default value is 30 seconds. A larger value may be needed when handling very big objects.
        /// </remarks>
        public int CommandTimeout
        {
            get { return selectValueCommand.CommandTimeout; }
            set
            {
                selectValueCommand.CommandTimeout = value;
                selectTimestampCommand.CommandTimeout = value;
                fileExistsCommand.CommandTimeout = value;
                insertCommand.CommandTimeout = value;
                getNamesCommand.CommandTimeout = value;
                deleteCommand.CommandTimeout = value;
                renameCommand.CommandTimeout = value;
            }
        }
 
        private SqlConnection _Connection;
        /// <summary>
        /// Gets or sets the connection object for database access.
        /// </summary>
        public SqlConnection Connection
        {
            get
            {
                return _Connection;
            }
            set
            {
                _Connection=value;
                CreateCommands();
            }
        }
 
        private string _TableName;
        /// <summary>
        /// Gets or sets the name of the table that stores the binary objects in the database.
        /// </summary>
        public string TableName
        {
            get
            {
                return _TableName;
            }
            set
            {
                _TableName=value;
                UpdateCommandTexts();
            }
        }
 
        private string _CachePath;
        /// <summary>
        /// Gets or sets the local cache path.
        /// </summary>
        /// <remarks>
        /// <para>If a relative path is specified, it will be combined with the value of the global variable <b>DataDirectory</b>,
        /// if it has a value at all. If not, the path will be combined with the application executable path. You can set the DataDirectory
        /// variable with this code: <code>AppDomain.CurrentDomain.SetData("DataDirectory", ruta)</code></para>
        /// <para>When retrieving the value, the full path is returned, with DataDirectory or the application path appropriately expanded.</para>
        /// </remarks>
        public string CachePath
        {
            get
            {
                return _CachePath;
            }
            set
            {
                string dataDirectory=(string)AppDomain.CurrentDomain.GetData("DataDirectory");
                if(dataDirectory==null)
                    dataDirectory=Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
                _CachePath=Path.Combine(dataDirectory, value);
                cacheDirectory=new DirectoryInfo(_CachePath);
            }
        }
 
        private string _NameColumn;
        /// <summary>
        /// Gets or sets the name of the column for the object name in the database table that stores the binary objects
        /// </summary>
        /// <remarks>
        /// Binary objects are uniquely identified by their names. This column should be defined with a "unique"
        /// constraint in the database, but this is not mandatory.
        /// </remarks>
        public string NameColumn
        {
            get
            {
                return _NameColumn;
            }
            set
            {
                _NameColumn=value;
                UpdateCommandTexts();
            }
        }
 
        private string _ValueColumn;
        /// <summary>
        /// Gets or sets the name of the column for the object contents in the database table that stores the binary objects
        /// </summary>
        /// <remarks>
        /// This column may be of any data type that ADO.NET can convert to and from byte arrays.
        /// </remarks>
        public string ValueColumn
        {
            get
            {
                return _ValueColumn;
            }
            set
            {
                _ValueColumn=value;
                UpdateCommandTexts();
            }
        }
 
        private string _TimestampColumn;
        /// <summary>
        /// Gets or sets the name of the column for the timestamp in the database table that stores the binary objects
        /// </summary>
        /// <remarks>
        /// This column may be of any data type that ADO.NET can convert to and from byte arrays.
        /// Also, the column value must automatically change when the value column changes.
        /// </remarks>
        public string TimestampColumn
        {
            get
            {
                return _TimestampColumn;
            }
            set
            {
                _TimestampColumn=value;
                UpdateCommandTexts();
            }
        }
 
        #endregion
 
        #region Constructors
 
        // Parameterless constructor is declared as private to avoid creating instances with no associated connection object
        private DatabaseFileCache() { }
 
        /// <summary>
        /// Creates a new instance of the class.
        /// </summary>
        /// <param name="connection">Connection object for database access.</param>
        /// <param name="tableName">Name of the table that stores the binary objects in the database.</param>
        /// <param name="cachePath">Local cache path (absolute or relative, see property CachePath).</param>
        /// <param name="nameColumn">Name of the column for the object name in the database table that stores the binary objects.</param>
        /// <param name="valueColumn">Name of the column for the object contents in the database table that stores the binary objects.</param>
        /// <param name="timestampColumn">Name of the column for the timestamp in the database table that stores the binary objects.</param>
        public DatabaseFileCache(SqlConnection connection, string tableName, string cachePath, string nameColumn, string valueColumn, string timestampColumn)
        {
            _TableName=tableName;
            CachePath=cachePath;
            _NameColumn=nameColumn;
            _ValueColumn=valueColumn;
            _TimestampColumn=timestampColumn;
            Connection=connection;
        }
 
        /// <summary>
        /// Creates a new instance of the class, assuming the default names <b>Name</b>, <b>Value</b> and <b>timestamp</b> for the names
        /// of the columns in the database table that stores the binary objects.
        /// </summary>
        /// <param name="connection">Connection object for database access.</param>
        /// <param name="tableName">Name of the table that stores the binary objects in the database.</param>
        /// <param name="cachePath">Local cache path (absolute or relative, see property CachePath).</param>
        public DatabaseFileCache(SqlConnection connection, string tableName, string cachePath)
            : this(connection, tableName, cachePath, "Name", "Value", "timestamp") { }
 
        /// <summary>
        /// Creates a new instance of the class, assuming the default names <b>Name</b>, <b>Value</b> and <b>timestamp</b> for the names.
        /// Also, assumes that the table name is <b>Objects</b>, and sets the local cache path to the relative name <b>DatabaseCache</b>
        /// (see property CachePath).
        /// </summary>
        /// <param name="connection">Connection object for database access.</param>
        public DatabaseFileCache(SqlConnection connection)
            : this(connection, "Objects", "DatabaseCache") { }
 
        #endregion
 
        #region Public methods
 
        /// <summary>
        /// Obtains a binary object from the local cache, retrieving it first from the database if necessary.
        /// </summary>
        /// <remarks>
        /// <para>
        /// A database connection is first established to check that an object with the specified name actually exists in the database.
        /// If not, <b>null</b> is returned.
        /// </para>
        /// <para>
        /// Then the local cache is examinated to see if the object has been already cached. If not, the whole object is
        /// retrieved from the database, the cached file is created, and the file path is returned.
        /// </para>
        /// <para>
        /// If the object was already cached, the timestamp of both the database object and the cached file are compared.
        /// If they are equal, the cached file path is returned directly. Otherwise, the cached file is recreated
        /// from the updated object data in the database.
        /// </para>
        /// </remarks>
        /// <param name="objectName">Name of the object to retrieve.</param>
        /// <returns>Full path of the cached file, or <i>null</i> if there is not an object with such name in the database.</returns>
        public string GetObject(string objectName)
        {
            Connection.Open();
            try
            {
                //* Obtain object timestamp from the database
 
                selectTimestampCommand.Parameters["@name"].Value=objectName;
                byte[] timestampBytes=(byte[])selectTimestampCommand.ExecuteScalar();
                if(timestampBytes==null)
                    return null;    //No object with such name found in the database
 
                string timestamp="";
                foreach(byte b in timestampBytes)
                    timestamp+=b.ToString("X").PadLeft(2, '0');
 
                //* Checks that the object is cached and that the cached file is up to date
 
                string escapedFileName=EscapeFilename(objectName);
                FileInfo[] fileInfos=cacheDirectory.GetFiles(EscapeFilename(objectName)+".*");
                if(fileInfos.Length>0)
                {
                    string cachedTimestamp=Path.GetExtension(fileInfos[0].Name);
                    if(cachedTimestamp==timestamp)
                        return fileInfos[0].FullName;   //Up to date cached version exists: return it
                    else
                        fileInfos[0].Delete();  //Outdated cached version exists: delete it
                }
 
                //* Object was not cached or cached file was outdated: retrieve it from database and cache it
 
                string fullLocalFileName=Path.Combine(CachePath, escapedFileName)+"."+timestamp;
                selectValueCommand.Parameters["@name"].Value=objectName;
                File.WriteAllBytes(fullLocalFileName, (byte[])selectValueCommand.ExecuteScalar());
 
                return fullLocalFileName;
            }
            finally
            {
                Connection.Close();
            }
        }
 
        /// <summary>
        /// Obtains the cached version of a database object, if it exists.
        /// </summary>
        /// <param name="objectName">Name of the object whose cached version is to be retrieved.</param>
        /// <returns>Full path of the cached file, or <i>null</i> if there the specified object is not cached.</returns>
        /// <remarks>
        /// This method does not access the database at all, it only checks the local cache.
        /// It should be used only when the database becomes unreachable, and only if it is acceptable
        /// to use data that may be outdated.
        /// </remarks>
        public string GetCachedFile(string objectName)
        {
            FileInfo[] fileInfos=cacheDirectory.GetFiles(EscapeFilename(objectName)+".*");
            if(fileInfos.Length>0)
                return fileInfos[0].FullName;
            else
                return null;
        }
 
        /// <summary>
        /// Creates or updates a binary object in the database from a byte array.
        /// </summary>
        /// <param name="value">Contents of the binary object.</param>
        /// <param name="objectName">Object name.</param>
        /// <remarks>
        /// If there is already an object with the specified name in the database, its contents are updated.
        /// Otherwise, a new object record is created.
        /// </remarks>
        public void SaveObject(byte[] value, string objectName)
        {
            insertCommand.Parameters["@name"].Value=objectName;
            insertCommand.Parameters["@value"].Value=value;
            Connection.Open();
            try
            {
                insertCommand.ExecuteNonQuery();
            }
            finally
            {
                Connection.Close();
            }
        }
 
        /// <summary>
        /// Creates or updates a binary object in the database from the contents of a file.
        /// </summary>
        /// <param name="filePath">Full path of the file containing the object data.</param>
        /// <param name="objectName">Object name.</param>
        /// <remarks>
        /// If there is already an object with the specified name in the database, its contents are updated.
        /// Otherwise, a new object record is created.
        /// </remarks>
        public void SaveObject(string filePath, string objectName)
        {
            SaveObject(File.ReadAllBytes(filePath), objectName);
        }
 
        /// <summary>
        /// Creates or updates a binary object in the database from the contents of a file,
        /// using the file name (without path) as the object name.
        /// </summary>
        /// <param name="filePath">Full path of the file containing the object data.</param>
        /// <remarks>
        /// If there is already an object with the specified name in the database, its contents are updated.
        /// Otherwise, a new object record is created.
        /// </remarks>
        public void SaveObject(string filePath)
        {
            SaveObject(filePath, Path.GetFileName(filePath));
        }
 
        /// <summary>
        /// Deletes an object from the database and from the local cache.
        /// </summary>
        /// <param name="objectName">Object name.</param>
        /// <remarks>
        /// If the object does not exist in the database, nothing happens and no error is returned.
        /// </remarks>
        public void DeleteObject(string objectName)
        {
            //* Delete object from database
 
            deleteCommand.Parameters["@name"].Value=objectName;
 
            Connection.Open();
            try
            {
                deleteCommand.ExecuteNonQuery();
            }
            finally
            {
                Connection.Close();
            }
 
            //* Delete object from local cache
 
            FileInfo[] files=cacheDirectory.GetFiles(EscapeFilename(objectName)+".*");
            foreach(FileInfo file in files) file.Delete();
        }
 
        /// <summary>
        /// Changes the name of an object in the database, and in the local cache.
        /// </summary>
        /// <param name="oldName">Old object name.</param>
        /// <param name="newName">New object name.</param>
        /// <remarks>
        /// If the object does not exist in the database, nothing happens and no error is returned.
        /// </remarks>
        public void RenameObject(string oldName, string newName)
        {
            //* Rename object in database
 
            renameCommand.Parameters["@oldName"].Value=oldName;
            renameCommand.Parameters["@newName"].Value=newName;
 
            Connection.Open();
            try
            {
                renameCommand.ExecuteNonQuery();
            }
            finally
            {
                Connection.Close();
            }
 
            //* Rename object in local cache
 
            string escapedOldName=EscapeFilename(oldName);
            string escapedNewName=EscapeFilename(newName);
 
            FileInfo[] files=cacheDirectory.GetFiles(escapedOldName+".*");
            foreach(FileInfo file in files)
            {
                string timestamp=Path.GetExtension(file.Name);
                file.MoveTo(Path.Combine(CachePath, escapedNewName+timestamp));
            }
        }
 
        /// <summary>
        /// Deletes all cached files that have no matching object in the database.
        /// </summary>
        /// <remarks>
        /// Cached files with no matching object in the database could appear if another user
        /// (or another application) deletes an object that was already cached.
        /// </remarks>
        public void PurgeCache()
        {
            List<string> databaseObjectNames=new List<string>(GetObjectNames());
            FileInfo[] files=cacheDirectory.GetFiles();
            foreach(FileInfo file in files)
            {
                if(!databaseObjectNames.Contains(UnescapeFilename(Path.GetFileNameWithoutExtension(file.Name))))
                    file.Delete();
            }
        }
 
        /// <summary>
        /// Checks whether an object exists in the database or not.
        /// </summary>
        /// <param name="objectName">Object name.</param>
        /// <returns><b>True</b> if there is an object with the specified name in the database, <b>False</b> otherwise.</returns>
        /// <remarks>
        /// The local cache is not accessed, only the database is checked.
        /// </remarks>
        public bool ObjectExists(string objectName)
        {
            fileExistsCommand.Parameters["@name"].Value=objectName;
            Connection.Open();
            try
            {
                int exists=(int)fileExistsCommand.ExecuteScalar();
                return exists==1;
            }
            finally
            {
                Connection.Close();
            }
        }
 
        /// <summary>
        /// Obtains the names of all the objects stored in the database.
        /// </summary>
        /// <returns>Names of all the objects stored in the database.</returns>
        /// <remarks>
        /// The local cache is not accessed, only the database is checked.
        /// </remarks>
        public string[] GetObjectNames()
        {
            List<string> names=new List<string>();
            Connection.Open();
            try
            {
                SqlDataReader reader=getNamesCommand.ExecuteReader();
                while(reader.Read())
                {
                    names.Add(reader.GetString(0));
                }
                reader.Close();
                return names.ToArray();
            }
            finally
            {
                Connection.Close();
            }
        }
 
        #endregion
 
        #region Private methods
 
        /// <summary>
        /// Escapes an object name so that it is a valid filename.
        /// </summary>
        /// <param name="fileName">Original object name.</param>
        /// <returns>Escaped name.</returns>
        /// <remarks>
        /// All characters that are not valid for a filename, plus "%" and ".", are converted into "%uuuu", where uuuu is the hexadecimal
        /// unicode representation of the character.
        /// </remarks>
        private string EscapeFilename(string fileName)
        {
            char[] invalidChars=Path.GetInvalidFileNameChars();
 
            // Replace "%", then replace all other characters, then replace "."
 
            fileName=fileName.Replace("%", "%0025");
            foreach(char invalidChar in invalidChars)
            {
                fileName=fileName.Replace(invalidChar.ToString(), string.Format("%{0,4:X}", Convert.ToInt16(invalidChar)).Replace(' ', '0'));
            }
            return fileName.Replace(".", "%002E");
        }
 
        /// <summary>
        /// Unescapes an escaped file name so that the original object name is obtained.
        /// </summary>
        /// <param name="escapedName">Escaped object name (see the EscapeFilename method).</param>
        /// <returns>Unescaped (original) object name.</returns>
        public string UnescapeFilename(string escapedName)
        {
            //We need to temporarily replace %0025 with %! to prevent a name
            //originally containing escaped sequences to be unescaped incorrectly
            //(for example: ".%002E" once escaped is "%002E%0025002E".
            //If we don't do this temporary replace, it would be unescaped to "..")
 
            string unescapedName=escapedName.Replace("%0025", "%!");
            Regex regex=new Regex("%(?<esc>[0-9A-Fa-f]{4})");
            Match m=regex.Match(escapedName);
            while(m.Success)
            {
                foreach(Capture cap in m.Groups["esc"].Captures)
                    unescapedName=unescapedName.Replace("%"+cap.Value, Convert.ToChar(int.Parse(cap.Value, NumberStyles.HexNumber)).ToString());
                m=m.NextMatch();
            }
            return unescapedName.Replace("%!", "%");
        }
 
        /// <summary>
        /// Creates the commands for database access.
        /// </summary>
        /// <remarks>
        /// This method is executed when the Connection property changes.
        /// </remarks>
        private void CreateCommands()
        {
            selectValueCommand=Connection.CreateCommand();
            selectValueCommand.Parameters.Add("@name", SqlDbType.NVarChar);
            selectTimestampCommand=Connection.CreateCommand();
            selectTimestampCommand.Parameters.Add("@name", SqlDbType.NVarChar);
            fileExistsCommand=Connection.CreateCommand();
            fileExistsCommand.Parameters.Add("@name", SqlDbType.NVarChar);
            insertCommand=Connection.CreateCommand();
            insertCommand.Parameters.Add("@name", SqlDbType.NVarChar);
            insertCommand.Parameters.Add("@value", SqlDbType.VarBinary);
            getNamesCommand=Connection.CreateCommand();
            deleteCommand=Connection.CreateCommand();
            deleteCommand.Parameters.Add("@name", SqlDbType.NVarChar);
            renameCommand=Connection.CreateCommand();
            renameCommand.Parameters.Add("@oldName", SqlDbType.NVarChar);
            renameCommand.Parameters.Add("@newName", SqlDbType.NVarChar);
 
            UpdateCommandTexts();
        }
 
        /// <summary>
        /// Updates the text of the commands used for database access.
        /// </summary>
        /// <remarks>
        /// This method is executed when any of these properties change: TableName, NameColumn, ValueColumn, TimestampColumn.
        /// </remarks>
        private void UpdateCommandTexts()
        {
            selectValueCommand.CommandText=string.Format(
                "select {0} from {1} where {2}=@name", ValueColumn, TableName, NameColumn);
 
            selectTimestampCommand.CommandText=string.Format(
                "select {0} from {1} where {2}=@name", TimestampColumn, TableName, NameColumn);
 
            fileExistsCommand.CommandText=string.Format(
                "if exists(select {0} from {1} where {0}=@name) select 1; else select 0;", NameColumn, TableName);
 
            insertCommand.CommandText=string.Format(
                "if exists (select {0} from {1} where {0}=@name) update {1} set {2}=@value where {0}=@name; else insert into {1} ({0}, {2}) values (@name, @value);",
                NameColumn, TableName, ValueColumn);
 
            getNamesCommand.CommandText=string.Format("select {0} from {1}", NameColumn, TableName);
 
            deleteCommand.CommandText=string.Format(
                "delete from {0} where {1}=@name", TableName, NameColumn);
 
            renameCommand.CommandText=string.Format(
                "update {0} set {1}=@newName where {1}=@oldName", TableName, NameColumn);
        }
 
        #endregion
    }
}

  

posted @   ®Geovin Du Dream Park™  阅读(276)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示