文章目录
- 前言
- 1.移动智能的三种模式
- 2.混合模式模式的瓶颈
- 3.数据同步的解决方案
- 一、关系数据库同步框架Dotmim.Sync的使用
- 1.包介绍
- 2.SqlServer到Sqlite同步测试基本使用
- 3.SqlServer到Sqlite同步测试Web API代理
- 3.1 服务端设置SqlServer同步服务
- 3.2 客户端设置SqlLite同步服务
- 3.2.1 普通客户端
- 3.2.2 MAUI安卓应用
- 总结
前言
1.移动智能的三种模式
众所周知,对于移动智能应用可以分为以下三种模式:
- 在线模式:在线模式下系统数据一般存储在服务器端的大中型数据库(如 SQL Server、Oracle、MySQL 等),移动应用依赖于稳定可靠的网络连接。
- 纯离线模式:纯离线模式下系统数据一般存储在移动终端的轻量级数据库(如 SQLite等),移动应用不需要网络连接。
- “在线+离线”混合模式:“在线+离线”混合模式则比较复杂,通常情况下系统数据存储在服务器端,移动终端暂存部分数据,因而形成了分布式异构数据库。
2.混合模式模式的瓶颈
在混合模式模式下当移动终端或服务器端执行数据更新操作后,为了保证数据的完整性和一致性,需要进行双向的数据同步。然而,由于移动网络本身具有复杂性、动态性、弱连接性以及通信延迟与带宽相对有限等特性,因而移动应用的数据同步技术备受考验。
3.数据同步的解决方案
DotMim.Sync(DMS)是用于同步关系数据库的简单框架,在.Net Standard 2.0之上开发,可在Xamarin,MAUI,.NET Core 3.1,.NET 6和7等中使用。
Dotmim.Sync框架包含针对多种不同主流关系数据库的子项目解决方案,每个子项目均发布为NuGet程序包,便于开发人员基于.NET平台在项目中添加、移除与更新引用。Nuget 上一共发布了8个Nuget包:
DotMim.Sync 的github网址:https://github.com/Mimetis/Dotmim.Sync
DotMim.Sync 的文档网址:https://dotmimsync.readthedocs.io/
一、关系数据库同步框架Dotmim.Sync的使用
1.包介绍
Dotmim.Sync.Core是核心的NuGet程序包,主要执行数据同步的核心逻辑。
以下是基于 .NET平台在服务器端与客户端程序中分别引用相应的NuGet程序包,进而完成服务器端与客户端数据库数据的同步:
- Dotmim.Sync.SqlServer:SQL Server数据库同步包
- Dotmim.Sync.Sqlite:SQLite数据库同步包
- Dotmim.Sync.MySql:MySQL数据库同步包
- Dotmim.Sync.MariaDB:MariaDB数据库同步包
- Dotmim.Sync.Web.Server 与 Dotmim.Sync.Web.Client NuGet:HTTP协议通过Web服务器完成服务器端与客户端数据库的同步操作
2.SqlServer到Sqlite同步测试基本使用
以.NET 7为例同步代码如下:
using Dotmim.Sync;
using Dotmim.Sync.Sqlite;
using Dotmim.Sync.SqlServer;SqlSyncProvider serverProvider = new SqlSyncProvider(@"Data Source=.;Initial Catalog=Dotmim;Integrated Security=true;User ID=sa;Password=1;");// Sqlite客户端提供程序充当“客户端”
SqliteSyncProvider clientProvider = new SqliteSyncProvider("Dotmim.db");//同步过程中涉及的表
//var setup = new SyncSetup("ProductCategory", "ProductDescription", "ProductModel",
// "Product", "ProductModelProductDescription", "Address", "Customer",
// "CustomerAddress", "SalesOrderHeader", "SalesOrderDetail");var setup = new SyncSetup("ProductCategory");
// 同步代理
SyncAgent agent = new SyncAgent(clientProvider, serverProvider);do
{var result = await agent.SynchronizeAsync(setup);Console.WriteLine(result);} while (Console.ReadKey().Key != ConsoleKey.Escape);
启动程序
尝试更新客户端或服务器数据库中的一行,然后再次按enter键
INSERT [dbo].[ProductCategory] VALUES(1,'sqlite-sync')
其他数据库类似就不做多介绍
3.SqlServer到Sqlite同步测试Web API代理
以下是web代理同步的整体框架图:
3.1 服务端设置SqlServer同步服务
创建 ASP.NET 应用程序后,我们将添加特定的 Web 服务器包和服务器提供程序:
- Dotmim.Sync.Web.Server:这个包将允许我们通过.Net核心Web API公开我们需要的一切
- Dotmim.Sync.SqlServer.ChangeTracking:此包将允许我们与SQL Server数据库进行通信。
在Program.cs添加如下代码
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));var connectionString = builder.Configuration.GetSection("ConnectionStrings")["SqlConnection"];//------------------------------------------第一种方式-------------------------------------
var options = new SyncOptions { };
// [Required] 同步过程中涉及的表
var tables = new string[] {"ProductCategory" };// [Required]: 添加充当服务器中心的SqlSyncProvider.
builder.Services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString, tables, options);//------------------------------------------第二种方式-------------------------------------
//var setup = new SyncSetup("ProductCategory");
//builder.Services.AddSyncServer<SqlSyncProvider>(connectionString, setup);//---------------------------------------多域配置------------------------------------------
//builder.Services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString,
// "prod", tables1, options);app.UseSession();
创建新控制器SyncController,单域使用
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{private WebServerAgent webServerAgent;private readonly IWebHostEnvironment env;// 创建数据同步控制器,采用依赖注入的方式注入服务器端Web 代理提供程序:public SyncController(WebServerAgent webServerAgent, IWebHostEnvironment env){this.webServerAgent = webServerAgent;this.env = env;}/// <summary>/// 在控制器的 POST 方法中调用 HandleRequestAsync 方法,执行异步请求,完成数据同步功能:/// </summary>/// <returns></returns>[HttpPost]public Task Post()=> webServerAgent.HandleRequestAsync(this.HttpContext);/// <summary>/// 此GET处理程序是可选的。它允许您查看服务器上托管的配置/// </summary>[HttpGet]public async Task Get(){if (env.IsDevelopment()){await this.HttpContext.WriteHelloAsync(webServerAgent);}else{var stringBuilder = new StringBuilder();stringBuilder.AppendLine("<!doctype html>");stringBuilder.AppendLine("<html>");stringBuilder.AppendLine("<title>Web Server properties</title>");stringBuilder.AppendLine("<body>");stringBuilder.AppendLine(" PRODUCTION MODE. HIDDEN INFO ");stringBuilder.AppendLine("</body>");await this.HttpContext.Response.WriteAsync(stringBuilder.ToString());}}
}
创建新控制器SyncController,多域使用
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{private IEnumerable<WebServerAgent> webserverAgents;private readonly IWebHostEnvironment env;// Injected thanks to Dependency Injectionpublic SyncController(IEnumerable<WebServerAgent> webServerAgents,IWebHostEnvironment env){this.webServerAgents = webServerAgents;this.env = env;}/// <summary>/// This POST handler is mandatory to handle all the sync process/// </summary>/// <returns></returns>[HttpPost]public Task Post(){var scopeName = HttpContext.GetScopeName();var webserverAgent = webserverAgents.FirstOrDefault(c => c.ScopeName == scopeName);await webserverAgent.HandleRequestAsync(HttpContext).ConfigureAwait(false);}/// <summary>/// This GET handler is optional./// It allows you to see the configuration hosted on the server/// </summary>[HttpGet]public async Task Get(){if (env.IsDevelopment()){await this.HttpContext.WriteHelloAsync(this.webserverAgents);}else{var stringBuilder = new StringBuilder();stringBuilder.AppendLine("<!doctype html>");stringBuilder.AppendLine("<html>");stringBuilder.AppendLine("<title>Web Server properties</title>");stringBuilder.AppendLine("<body>");stringBuilder.AppendLine(" PRODUCTION MODE. HIDDEN INFO ");stringBuilder.AppendLine("</body>");await this.HttpContext.Response.WriteAsync(stringBuilder.ToString());}}}
3.2 客户端设置SqlLite同步服务
3.2.1 普通客户端
var serverOrchestrator = new WebRemoteOrchestrator("https://localhost:44342/api/sync");// Second provider is using plain old Sql Server provider,
// relying on triggers and tracking tables to create the sync environment
var clientProvider = new SqlSyncProvider(clientConnectionString);// Creating an agent that will handle all the process
var agent = new SyncAgent(clientProvider, serverOrchestrator);do
{// Launch the sync processvar s1 = await agent.SynchronizeAsync();// Write resultsConsole.WriteLine(s1);} while (Console.ReadKey().Key != ConsoleKey.Escape);Console.WriteLine("End");
现在我们可以启动两个应用程序,一端是 Web Api,另一端是控制台应用程序。 只需按 输入 并通过 http 获取同步结果。
3.2.2 MAUI安卓应用
1、在项目的AndroidManifest.xml文件中添加网络访问、读写外部存储等权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><application android:networkSecurityConfig="@xml/network_security_config" android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.INTERNET" />
</manifest>
2、由于Google 从Android P开始已经明确规定禁止http协议额,但是我们的接口都是http协议,从Nougat(Android 7)一个名为“Network Security Configuration”的新安全功能也随之而来。网络安全性配置特性让应用可以在一个安全的声明性配置文件中自定义其网络安全设置,而无需修改应用代码。
10.10.10.10为连接到webapi的地址
<?xml version="1.0" encoding="utf-8"?><network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">10.10.10.10</domain> </domain-config>
</network-security-config>
3、在数据同步事件中,开启子线程,在子线程中执行数据同步操作:
handler.AutomaticDecompression = DecompressionMethods.Gzip;this.httpClient = new HttpClient(handler);
// Check if we are trying to reach a IIs Express
//IIs Express does not allow any request other than localhost
// So far,hacking the Host-Content header to mimic localhost call
if(DeviceInfo.Platform == DevicePlatform.Android && syncApiUri.Host == "10.10.10.10")this.httpClient.DefaultRequestHeaders.Host = $"localhost:{syncApiUri.Port}";
this.httpclient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
this.webProxyProvider = new WebRemote0rchestrator(this.settings.SyncApiUrl, client: this.httpClient);
this.sqliteSyncProvider =new SqliteSyncProvider(this.settings.DataSource);
var clientOptions = new SyncOptions { BatchSize = settings.BatchSize, BatchDirectory = settings.BatchDirectory }
this.syncAgent = new SyncAgent(sqliteSyncProvider, webProxyProvider, clientoptions);
private async Task ExecuteSyncCommand(SyncType syncType){IsBusy = true;var progress = new Progress<ProgressArgs>(args =>{MainThread.BeginInvokeOnMainThread(() =>this.SyncProgress = args.ProgressPercentage;this.SyncProgressionText = args.Message;});});var r = await this.syncAgent.SynchronizeAsync(syncType, progress);MainThread.BeginInvokeOnMainThread(() => this.SyncProgressionText = r.ToString());
}
总结
执行数据同步的常规过程,由客户端发起数据同步 POST 请求,服务器端.NET Core Web API尝试执行数据同步任务。其次,当检测到数据冲突时,服务器端检测预先设置的 ConflictResolutionPolicy 属性值,如果其值为 Serverwins,则服务器端获胜,将服务器端的变化数据强制应用到客户端的数据库中,反之则客户端获胜,将客户端的变化数据强制应用到服务器端的数据库中。
- 数据同步方向在 Dotmim.Sync 框架中,提供了用于表征数据同步方向的枚举 SyncDirection。该枚举包含 3 个值:Bidirectional(默认值)、DownloadOnly和Upload⁃Only,分别对应“双向同步”、“仅下载同步”与“仅上传同步”3 种方向,可以具体为每个数据表SetupTable分别设定同步方向。
- 通常情况下冲突问题解决Dotmim.Sync 框架采用 SyncOption 对象的配置策略属性ConflictResolutionPolicy解决数据冲突问题。ConflictResolutionPolicy的可选项如下:
- ConflictResolutionPolicy.Serverwins, 默认选项,表征服务端为所有冲突的获胜方。
- ConflictResolutionPolicy.Clientwins 表征客户端为所有冲突的获胜方