当前位置: 首页>后端>正文

C#基础巩固与进阶(定时语音播报+PDF水印+分片上传+EF6框架)

b站视频:2022年C#进阶教程-C#应该学到什么程度(针对编程思维)

前提

UI框架:WinForm(基于.net framework 4.6.1)、MaterialSkin.2(v2.3.0.0)

//第一步 NuGet下载MaterialSkin.2

// 第二步 
// 初始化MaterialSkinManager
 MaterialSkinManager materialSkinManager = MaterialSkinManager.Instance;

 //将此设置为false,以禁用对非材质蒙皮组件的背景色强制
 //这必须在AddFormToManager()之前设置
 materialSkinManager.EnforceBackcolorOnAllComponents = true;

 //MaterialSkinManager 属性
 materialSkinManager.AddFormToManage(this);
 materialSkinManager.Theme = MaterialSkinManager.Themes.LIGHT;
 materialSkinManager.ColorScheme = new ColorScheme(Primary.Teal400, Primary.Teal200, Primary.Teal400, Accent.DeepOrange100, TextShade.WHITE);

ps:如果你想使用wpf到达类似界面效果可以参考。
https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit

章节

1.程序员福音-定时提醒(C#版)

会使用自定义控件

ps:等到后面学习ef6的时候 我们把数据存储到数据库里面 以便打开时能查询到我们设置的任务

2.C#使用Apose.PDF给Pdf文件添加自定义水印

依赖:Aspose.Pdf (v10.0.0.0)

官网:https://docs.aspose.com/pdf/net/

Stream LStream = new MemoryStream(Convert.FromBase64String(ConfigurationManager.AppSettings.Get("aposeLicense")));
new Aspose.Pdf.License().SetLicense(LStream);

ps:仅支持10.0.0.0版本以上

破解版秘钥许可:
PExpY2Vuc2U+DQogIDxEYXRhPg0KICAgIDxMaWNlbnNlZFRvPkFzcG9zZSBTY290bGFuZCBUZWFtPC9MaWNlbnNlZFRvPg0KICAgIDxFbWFpbFRvPmJpbGx5Lmx1bmRpZUBhc3Bvc2UuY29tPC9FbWFpbFRvPg0KICAgIDxMaWNlbnNlVHlwZT5EZXZlbG9wZXIgT0VNPC9MaWNlbnNlVHlwZT4NCiAgICA8TGljZW5zZU5vdGU+TGltaXRlZCB0byAxIGRldmVsb3BlciwgdW5saW1pdGVkIHBoeXNpY2FsIGxvY2F0aW9uczwvTGljZW5zZU5vdGU+DQogICAgPE9yZGVySUQ+MTQwNDA4MDUyMzI0PC9PcmRlcklEPg0KICAgIDxVc2VySUQ+OTQyMzY8L1VzZXJJRD4NCiAgICA8T0VNPlRoaXMgaXMgYSByZWRpc3RyaWJ1dGFibGUgbGljZW5zZTwvT0VNPg0KICAgIDxQcm9kdWN0cz4NCiAgICAgIDxQcm9kdWN0PkFzcG9zZS5Ub3RhbCBmb3IgLk5FVDwvUHJvZHVjdD4NCiAgICA8L1Byb2R1Y3RzPg0KICAgIDxFZGl0aW9uVHlwZT5FbnRlcnByaXNlPC9FZGl0aW9uVHlwZT4NCiAgICA8U2VyaWFsTnVtYmVyPjlhNTk1NDdjLTQxZjAtNDI4Yi1iYTcyLTdjNDM2OGYxNTFkNzwvU2VyaWFsTnVtYmVyPg0KICAgIDxTdWJzY3JpcHRpb25FeHBpcnk+MjAxNTEyMzE8L1N1YnNjcmlwdGlvbkV4cGlyeT4NCiAgICA8TGljZW5zZVZlcnNpb24+My4wPC9MaWNlbnNlVmVyc2lvbj4NCiAgICA8TGljZW5zZUluc3RydWN0aW9ucz5odHRwOi8vd3d3LmFzcG9zZS5jb20vY29ycG9yYXRlL3B1cmNoYXNlL2xpY2Vuc2UtaW5zdHJ1Y3Rpb25zLmFzcHg8L0xpY2Vuc2VJbnN0cnVjdGlvbnM+DQogIDwvRGF0YT4NCiAgPFNpZ25hdHVyZT5GTzNQSHNibGdEdDhGNTlzTVQxbDFhbXlpOXFrMlY2RThkUWtJUDdMZFRKU3hEaWJORUZ1MXpPaW5RYnFGZkt2L3J1dHR2Y3hvUk9rYzF0VWUwRHRPNmNQMVpmNkowVmVtZ1NZOGkvTFpFQ1RHc3pScUpWUVJaME1vVm5CaHVQQUprNWVsaTdmaFZjRjhoV2QzRTRYUTNMemZtSkN1YWoyTkV0ZVJpNUhyZmc9PC9TaWduYXR1cmU+DQo8L0xpY2Vuc2U+

3.C#实现文件分片上传,前端winform+后端.net core api

简述分片上传

所谓分片上传,也就是把文件分成一小片,形成多个文件,上传后服务器将文件组成一个完整的文件,当然此时需要校验文件的hashcode保证上传与组成的文件是同一个文件

.net core 3.1 关于文件上传:
https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming

public static  string UploadFileHttpRequest(string url,byte[] fileBytes, string fileName, string name = "files"
                                                    , Dictionary<string,string> paramDict=null)
        {
            using (HttpClient client = new HttpClient())
            {
                try
                {
                    var content = new MultipartFormDataContent();
                    if (paramDict != null)
                    {
                        foreach (var item in paramDict)
                        {
                            content.Add(new StringContent(item.Value), item.Key);
                        }
                    }
                    // 上传文件类型 
                    content.Add(new ByteArrayContent(fileBytes), name, fileName);

                    string result = client.PostAsync(url, content).Result.Content.ReadAsStringAsync().Result;
                    return result;
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }

        }


/// <summary>
/// 分片上传(未记录文件hash码)
/// </summary>
/// <param name="url">上传url</param>
/// <param name="filePath">文件位置</param>
/// <param name="sectionLen">每片长度(按M为单位)</param>
/// <param name="name">文件后端接收映射的key值</param>
/// <param name="paramDict">相关参数</param>
/// <returns></returns>
public static string UploadSectionFile(string url, string filePath, int sectionLen = 5, string name = "file"
                                               , Dictionary<string, string> paramDict = null)
        {
            FileInfo file = new FileInfo(filePath);
            string result=null;
            int partLen = 1024 * 1024 * sectionLen;//5M
            long total = file.Length % partLen == 0 file.Length / partLen : file.Length / partLen + 1;
            byte[] sends = new byte[partLen];
            int restLen = (int)(file.Length % partLen);
            int position = 0;
            using (FileStream fileStream = file.OpenRead())
            {
                for (int i = 0; i < total; i++)
                {
                    if (i == total-1 && restLen > 0)
                    {
                        sends = new byte[restLen];
                        partLen = restLen;
                    }
                    fileStream.Read(sends, 0, partLen);
                    paramDict = paramDictnew Dictionary<string, string>();
                    if (paramDict.ContainsKey("position"))
                    {
                        paramDict.Remove("position");
                    }
                    paramDict.Add("position", position + "");//记录文件位置
                    result = UploadFileHttpRequest(url, sends, file.Name, name, paramDict);
                    position += partLen;//位置
                }
            }
            return result;
        }
 /// <summary>
        /// 分片上传
        /// </summary>
        /// <returns></returns>
        [HttpPost("partUpload")]
        public async Task<IActionResult> OnPostPartUploadAsync(IFormFile file,[FromForm]long position)
        {         
            if (file?.Length > 0)
            {
                long size = file.Length;
                string filePath = Path.Combine(@"F:\", file.FileName);//可以修改保存路径
                using (var stream = System.IO.File.Open(filePath, FileMode.OpenOrCreate))
                {
                    stream.Seek(position, SeekOrigin.Begin);
                    byte[] reads = new byte[1024];
                    using (var inputStream = file.OpenReadStream())
                    {
                        int readV;
                        while ((readV = inputStream.Read(reads)) > 0)
                        {
                            await stream.WriteAsync(reads, 0, readV);
                        }
                    }
                }
                return Ok(new { msg = "上传成功", position, size });
            }

            return Ok(new { msg = "没有文件需要上传", count = 0 });
        }

4.winform的数据绑定

//方式一
xxControl.DataBindings.Add(binding);

//方式二
xxControl.DataBindings.Add(propName,dataSource,memberName);

5.EF6框架(EntityFramework)

需要三张表: t_user_info t_book_info t_borrow_info

5.1 官方地址

源码地址: https://github.com/dotnet/ef6/tree/main/src
文档地址: https://learn.microsoft.com/zh-cn/ef/ef6/

5.2 相关集成(配置文件+代码层面)

本地 SQL Server数据库链接:

Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=C:\Users\CNC\Desktop\MusicDBContext.mdf;Initial Catalog=MusicDBContext;Integrated Security=True
Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=FileApplication.MyDbContext;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False

  • 第一步 NuGet安装核心依赖项

    **第一种:轻便型**
    

    NuGet安装 EntityFramework(v6.2.0)、MySQL.Data.Entities(v6.8.3)


<connectionStrings>
  <add name="mysqlCon" connectionString="server=127.0.0.1;port=3306;user=root;password=123456; database=book_manage;Charset=utf8" providerName="MySql.Data.MySqlClient" />
</connectionStrings>


<entityFramework>
  <!--MySql.Data.MySqlClient部分安装后需要自己配置,而System.Data.SqlClient是安装依赖后自己生成的-->
  <providers>
    <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />       
  </providers>
</entityFramework>

<!--安装依赖后自动生成的-->
<system.data>
  <DbProviderFactories>
    <remove invariant="MySql.Data.MySqlClient" />
    <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.8.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
  </DbProviderFactories>
</system.data>

第二种: 方便型
(仅安装MySql.Data.EntityFramework,缺点是安装的关联依赖很多,见下图)
直接NuGet安装MySql.Data.EntityFramework(v8.0.30)它会把关联的依赖项加入 如MySql.Data.DLL(8.0.30) + EntityFramework .DLL (v6.2.0)+EntityFramework.SqlServer.DLL (v6.2.0)

  • [图片上传失败...(image-136f33-1668222314579)]
<connectionStrings>
  <!--mysql 连接信息-->
  <add name="mysqlCon" connectionString="server=127.0.0.1;user id=root;password=123456;database=smart-parking;sslmode=none;charset=utf8" providerName="MySql.Data.MySqlClient" />
</connectionStrings>

<system.data>
  <DbProviderFactories>
    <remove invariant="MySql.Data.MySqlClient" />
    <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory,MySql.Data, Version=8.0.30.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
  </DbProviderFactories>
</system.data>
  • 第二步
    建立实体 DbContext里面具体数据集

5.3 相关特性(注解)

约束注解:

https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations.schema?view=net-6.0

组件模型注解:

https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-6.0&viewFallbackFrom=entity-framework-6.2.0

5.4 日志打印

自定义 DatabaseLogFormatter
通过创建一个派生自 DatabaseLogFormatter 并适当替代方法的新类,可以更改记录的内容及其格式。 最常见的替代方法是:

  • LogCommand - 替代此选项可更改命令在执行前的记录方式。 默认情况下,LogCommand 会针对每个参数调用 LogParameter;可选择在替代中执行相同的操作或以不同的方式处理参数。
  • LogResult - 替代此选项可更改执行命令结果的记录方式。
  • LogParameter - 替代此选项可更改参数记录的格式和内容。

例如,假设我们只想在每个命令发送到数据库之前记录一行。 可通过两个替代来完成:

  • 替代 LogCommand 以格式化和写入单行 SQL
  • 替代 LogResult,不执行任何操作。

代码将如下所示:

public class OneLineFormatter : DatabaseLogFormatter
{
    public OneLineFormatter(DbContext context, Action<string> writeAction)
        : base(context, writeAction)
    {
    }

    public override void LogCommand<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
        Write(string.Format(
                "Context '{0}' , Executing Command:\r\n '{1}'{2}",
                Context.GetType().Name,
                command.CommandText.Replace(Environment.NewLine, ""),
                Environment.NewLine));
    }

    public override void LogResult<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
    }
}

若要记录输出,只需调用 Write 方法,该方法会将输出发送到配置的写入委托。
(请注意,此代码会简单删除换行符,就像示例一样。它可能无法很好地查看复杂的 SQL.)

设置 DatabaseLogFormatter
创建一个新的 DatabaseLogFormatter 类后,需要向 EF 注册该类。 使用基于代码的配置完成此操作。 简而言之,这意味着在与 DbContext 类相同的程序集中创建一个派生自 DbConfiguration 的新类,然后在这个新类的构造函数中调用 SetDatabaseLogFormatter。 例如:

public class MyDbConfiguration : DbConfiguration
{
    public MyDbConfiguration()
    {
        SetDatabaseLogFormatter(
            (context, writeAction) => new OneLineFormatter(context, writeAction));
    }
}

5.5 CRUD+事务

5.5.1 封装的DbSet

https://learn.microsoft.com/zh-cn/dotnet/api/system.data.entity.dbset-1?source=recommendations&view=entity-framework-6.2.0

//单个新增
//将给定实体以“已添加”状态添加到集的基础上下文中,这样一来,当调用 SaveChanges 时,会将该实体插入到数据库中。
DataSet.Add(Object);


//多个新增
//将给定的实体集合添加到该集的上下文中,并将每个实体放入“添加”状态,以便调用 SaveChanges 时,该实体将插入数据库。
DataSet.AddRange(IEnumerable)   

//将在此上下文中所做的所有更改保存到基础数据库。
DbContext.SaveChanges() 

//扩展方法
//需要引入命名空间: System.Data.Entity.Migrations
DataSet.AddOrUpdate(TEntity);
//将在此上下文中所做的所有更改保存到基础数据库。
DbContext.SaveChanges(TEntity[]);    
//单个删除
//将给定实体标记为“已删除”,这样一来,当调用 SaveChanges 时,将从数据库中删除该实体。 请注意,在调用此方法之前,该实体必须以另一种状态存在于该上下文中。
DataSet.Remove(Object)  

//多个删除
//从设置的上下文中删除给定的实体集合,并将每个实体放入 Deleted 状态,以便调用 SaveChanges 时,将从数据库中删除该实体。
DataSet.RemoveRange(IEnumerable)    

//将在此上下文中所做的所有更改保存到基础数据库。
DbContext.SaveChanges() 
//方式一
//创建一个原始 SQL 查询,该查询将返回此集中的实体。
DataSet.SqlQuery(String, Object[]);

示例:SqlQuery ("SELECT * FROM dbo.表名 WHERE Author = @author", new SqlParameter ("@author", userSuppliedAuthor) ) ;

//方式二
DataSet.Find(Object[]);

5.5.2 原生SQL

DataSet.SqlQuery("dbo.表名");//sql不带参数

DataSet.SqlQuery("dbo.表名",参数数组);//sql带参数
DataSet.ExecuteSqlCommand(新增/修改/删除语句, Object[]);
//object[] 对应的类型=>new SqlParameter ("@author", userSuppliedAuthor)

5.5.3 自动检测更改

//如果跟踪上下文中的大量实体,并且在循环中多次调用其中某个方法,则可关闭在循环期间的更改检测来获得显著的性能改进。 例如:

using (var context = new DbContext())
{
    try
    {
        context.Configuration.AutoDetectChangesEnabled = false;
        // 在循环中进行多次调用(Add方法)
        foreach (var blog in aLotOfBlogs)
        {
            context.Blogs.Add(blog);
        }
    }
    finally
    {
        context.Configuration.AutoDetectChangesEnabled = true;
    }
}
//不要忘记在循环后重新启用更改检测 - 我们使用了 try/finally 来确保始终重新启用更改,即使循环中的代码引发异常。

//禁用和重新启用的替代方法是,使自动检测更改一直保持关闭状态,并且显式调用context.ChangeTracker.DetectChanges或努力使用更改跟踪代理。 这两个都是高级选项,可以轻松地在应用程序中引入细微 bug,因此请谨慎使用它们。

//如果需要在上下文中添加或删除多个对象,请考虑使用 DbSet.AddRange 和 DbSet.RemoveRange。 此方法仅在添加或删除操作完成后自动检测更改一次。

5.5.4 使用事务:

从 EF6 开始,框架现在提供:

  1. Database.BeginTransaction():一种更简单的方法,让用户在现有的 DbContext 中自己启动和完成事务 - 允许在同一事务中合并多个操作,因此所有已提交或所有回滚都为一个事务。 它还允许用户更轻松地指定事务的隔离级别。
  2. Database.UseTransaction():它允许 DbContext 使用在实体框架外部启动的事务。

5.5.4 处理并发冲突

方案一:通过 Reload(数据库优先)解决乐观并发异常
方案二:在客户端优先时解决乐观并发异常
方案三:自定义乐观并发异常的解决方案
方案四:使用对象自定义乐观并发异常的解决方案

5.6 拓展(关于迁移)

选项一:使用现有架构作为起点

Code First 迁移使用存储在最近迁移中的模型快照来检测模型的更改(可以在团队环境中的 Code First 迁移中找到关于此的详细信息)。 由于我们将假设数据库已拥有当前模型的架构,因此我们将生成一个空(无操作)迁移,该迁移将当前模型作为快照。

  1. 在包管理器控制台中运行 Add-Migration InitialCreate –IgnoreChanges 命令。 这将创建一个以当前模型作为快照的空迁移。
  2. 在包管理器控制台运行 Update-Database 命令。 这会将 InitialCreate 迁移应用到数据库。 由于实际迁移不包含任何更改,因此它只会向 __MigrationsHistory 表添加一行,指示已应用此迁移。
  3. [图片上传失败...(image-48a274-1668222314579)]

选项二:使用空数据库作为起点

在这个场景中,我们需要迁移能够从头开始创建整个数据库,包括本地数据库中已存在的表。 我们将生成一个 InitialCreate 迁移,其中包含用于创建现有架构的逻辑。 然后,我们将使现有数据库看起来就像已应用此迁移一样。

  1. 在包管理器控制台中运行 Add-Migration InitialCreate 命令。 这将创建一个迁移以创建现有架构。

[图片上传失败...(image-104fb8-1668222314579)]
[图片上传失败...(image-3cd56e-1668222314579)]

  1. 注释掉新创建迁移的 Up 方法中的所有代码。 这样便可以将迁移“应用”到本地数据库,而无需尝试重新创建所有已经存在的表等。
  2. 在包管理器控制台运行 Update-Database 命令。 这会将 InitialCreate 迁移应用到数据库。 由于实际迁移不包含任何更改(因为已暂时将其注释掉),因此它只会向 __MigrationsHistory 表添加一行,指示已应用此迁移。

[图片上传失败...(image-40a36-1668222314579)]
[图片上传失败...(image-dfa547-1668222314579)]

  1. 取消注释 Up 方法中的代码。 这意味着,当此迁移应用于将来的数据库时,本地数据库中已经存在的架构将由迁移创建。

EF6: 创建 mysql 迁移文件报错

未为提供程序“MySql.Data.MySqlClient”找到任何 MigrationSqlGenerator。请在目标迁移配置类中使用 SetSqlGenerator 方法以注册其他 SQL 生成器。
[图片上传失败...(image-d44b9e-1668222314579)]
解决:
添加特性:
[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]

关于注解的详细使用:

https://learn.microsoft.com/zh-cn/ef/ef6/modeling/code-first/data-annotations

关于Mysql(EF6的支持)
https://dev.mysql.com/doc/connector-net/en/connector-net-entityframework60.html


https://www.xamrdz.com/backend/3kr1940111.html

相关文章: