不管是ClickOnce发布还是生成单个文件的发布都是全量更新,当引用的nuget包较多的时候,重复上传和下载的内容就比较多,所以需要增量更新。

一、在数据库增加一个表

CREATE TABLE [dbo].[TApp](
	[Version] [int] IDENTITY(1,1) NOT NULL,
	[Data] [varbinary](max) NOT NULL,
 CONSTRAINT [PK_TApp] PRIMARY KEY CLUSTERED 
(
	[Version] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

就两列,Version列存放自增长版本号,Data列存放更新的数据。
二、发布

        /// <summary>
        /// 发布
        /// </summary>
        /// <param name="path">发布的目标地址</param>
        /// <returns></returns>
        public static void Publish(string path)
        {
            if (!Directory.Exists(path))
            {
                MessageBox.Show("目标地址不存在");
                return;
            }
            var pathLast = Path.Combine(path, "Last");//保存最后一次发布的文件的文件夹
            if (!Directory.Exists(pathLast))
            {
                Directory.CreateDirectory(pathLast);
                PathCopy(path, pathLast);
                MessageBox.Show("没有需要发布的内容");
                return;
            }

            List<KeyValuePair<string, byte[]>> items = new List<KeyValuePair<string, byte[]>>();
            foreach (var fullFilename in Directory.GetFiles(path))
            {
                var filename = Path.GetFileName(fullFilename);
                var lastFilename = Path.Combine(pathLast, filename);
                var buffer = File.ReadAllBytes(fullFilename);
                if (File.Exists(lastFilename))
                {
                    var lastBuffer = File.ReadAllBytes(lastFilename);
                    if (buffer.SequenceEqual(lastBuffer))
                        continue;
                }
                File.WriteAllBytes(lastFilename, buffer);
                items.Add(new KeyValuePair<string, byte[]>(filename, buffer));
            }
            if (items.Count == 0)
            {
                MessageBox.Show("没有需要发布的内容");
                return;
            }


            #region 压缩一下
            var jsonString = JsonSerializer.Serialize(items);
            using MemoryStream instream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
            using MemoryStream outstream = new MemoryStream();
            using DeflateStream defstream = new DeflateStream(outstream, CompressionMode.Compress);
            instream.CopyTo(defstream);
            defstream.Close();
            var data = outstream.ToArray();
            #endregion





            using SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;initial Catalog=DBApp;integrated Security=True");
            var cmd = cn.CreateCommand();
            cmd.CommandText = "insert into tapp values (@Data)";
            cmd.Parameters.AddWithValue("@Data", data);
            cn.Open();
            cmd.ExecuteNonQuery();

            MessageBox.Show("发布成功");
        }
        /// <summary>
        /// 复制文件夹
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        private static void PathCopy(string source, string destination)
        {
            foreach (var filename in Directory.GetFiles(source))
                File.Copy(filename, Path.Combine(destination, Path.GetFileName(filename)), true);
        }

三、客户端更新

        public static void Update()
        {
            var currentPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
            var filenameVersion = Path.Combine(currentPath, "version.txt");
            int clientVersion = 0;
            if (File.Exists(filenameVersion))
                clientVersion = int.Parse(File.ReadAllText(filenameVersion));


            using SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;initial Catalog=DBApp;integrated Security=True");
            cn.Open();
            var cmd = cn.CreateCommand();



            #region 获取需要更新的内容
            List<KeyValuePair<int, byte[]>> kvs = new List<KeyValuePair<int, byte[]>>();
            cmd.CommandText = $"select * from tapp where version>{clientVersion}";
            var dr = cmd.ExecuteReader();
            while (dr.Read())
                kvs.Add(new KeyValuePair<int, byte[]>(dr.GetInt32(0), (byte[])dr[1]));
            dr.Close();
            if (kvs.Count == 0)
            {
                MessageBox.Show("没有需要更新的内容");
                return;
            }
            #endregion


            var updatePath = Path.Combine(currentPath, "U" + DateTime.Now.Ticks);
            Directory.CreateDirectory(updatePath);

            foreach (var kv in kvs)
            {
                #region 解压
                using MemoryStream ms = new MemoryStream(kv.Value);
                using MemoryStream ms2 = new MemoryStream();
                using DeflateStream defStram = new DeflateStream(ms, CompressionMode.Decompress);
                defStram.CopyTo(ms2);
                defStram.Close();
                var buffer = ms2.ToArray();
                #endregion

                var jsonString = Encoding.UTF8.GetString(buffer);
                var items = JsonSerializer.Deserialize<List<KeyValuePair<string, byte[]>>>(jsonString);
                foreach (var item in items)
                    File.WriteAllBytes(Path.Combine(updatePath, item.Key), item.Value);
            }

            File.WriteAllText(filenameVersion, kvs.Max(ii => ii.Key).ToString());//更新客户端版本号


            #region 生成并运行bat文件
            var batstr = $@"timeout /t 3
taskkill /pid {Process.GetCurrentProcess().Id}
copy {updatePath} {currentPath}
del {updatePath} /Q
rd {updatePath}
{Process.GetCurrentProcess().MainModule.FileName}
";
            var filenameBat = Path.Combine(currentPath, "update.bat");
            File.WriteAllText(filenameBat, batstr);
            App.Current.Shutdown();
            Process.Start(filenameBat);
            #endregion
        }

四、测试一下

添加两个按钮,对应的click事件如下:

        private void BtnPublish_Click(object sender, RoutedEventArgs e)
        {
            Publish(@"D:\TestHot\ConsoleApp1\WpfUpdate\bin\Release\net5.0-windows\publish");
        }

        private void BtnUpdate_Click(object sender, RoutedEventArgs e)
        {
            Update();
        }

最后就是vs发布、单击发布按钮、单击更新按钮...