2021年6月

在 VSCode 中调试 Vue

VSCode launch.json配

{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-chrome",
      "request": "launch",
      "name": "Launch Chrome against localhost",
      "url": "http://localhost:9527",
      "port": 9000, // 指定调试端口,没有被占用就行
      // 项目根路径
      "webRoot": "${workspaceFolder}/src",
      // webpack路径映射
      "sourceMapPathOverrides": {
        "webpack:///./src/*": "${webRoot}/*",
        "webpack:///src/*": "${webRoot}/*"
      },
      // 浏览器路径
      "runtimeExecutable": "D:\\Users\\EDZ\\AppData\\Local\\360Chrome\\Chrome\\Application\\360chrome.exe"
    }
  ]
}

(可选)项目 package.json 配置

{
  "scripts": {
    "start": "NODE_PATH=./src DEBUG=express:* NODE_ENV=dev nodemon -w src --ext ts --exec node --inspect -r tsconfig-paths/register -r ts-node/register src/index.ts",
    "testDebug": "NODE_ENV=test ts-mocha --debug-brk=9000 --paths -p tsconfig.json"
  }
}
参考
在 VS Code 中调试
在VScode和Chrome中调试Vue,未绑定断点

问题背景

Windows笔记本上新拉完代码,在执行pre-commit时,出现如下错误:

Delete `␍`eslint(prettier/prettier)

下面是几种个人尝试过的解决方案:

解决方案

一、Crtl+S保存文件

Crtl+S保存当前报错文件,eslint错误消失,但是Git暂存区多了个文件改动记录,对比Working tree没发现任何不同。

缺点:你不可能一一保存所有文件,麻烦,还要commit,多余。

二、yarn run lint --fix

比上面省事,eslint错误消失,但暂存区多了n个文件改动记录,对比Working tree也没发现任何不同。

缺点:需要commit所有文件,多余。
参考资料:"error Delete ⏎ prettier/prettier" in .vue files

三、配置.prettierrc文件

在项目根目录下的.prettierrc文件中写入即可。其实就是不让prettier检测文件每行结束的格式.

"endOfLine": "auto"

缺点:不能兼容跨平台开发,从前端工程化上讲没有做到尽善尽美。
[参考资料:Why do I keep getting Delete ‘cr’ [prettier/prettier]?](https://stackoverflow.com/questions/53516594/why-do-i-keep-getting-delete-cr-prettier-prettier "参考资料:Why do I keep getting Delete ‘cr’ [prettier/prettier]?")

四、通过IDE一键切换

这是评论区掘金同道提供的方法,一般IDE下方状态栏会提供“切换行尾序列”的工具,如上图,一键切换到正确的行尾序列即可。

缺点:只能修复当前文件,不能解决整个项目报错的问题。

五、最佳方案

问题根源:
罪魁祸首是git的一个配置属性:core.autocrlf
由于历史原因,windows下和linux下的文本文件的换行符不一致。

Windows在换行的时候,同时使用了回车符CR(carriage-return character)和换行符LF(linefeed character)
而Mac和Linux系统,仅仅使用了换行符LF
老版本的Mac系统使用的是回车符CR

WindowsLinux/MacOld Mac(pre-OSX)
CRLFLFCR
‘\n\r’‘\n’'\r'

因此,文本文件在不同系统下创建和使用时就会出现不兼容的问题。

我的项目仓库中默认是Linux环境下提交的代码,文件默认是以LF结尾的(工程化需要,统一标准)。

当我用windows电脑git clone代码的时候,若我的autocrlf(在windows下安装git,该选项默认为true)为true,那么文件每行会被自动转成以CRLF结尾,若对文件不做任何修改,pre-commit执行eslint的时候就会提示你删除CR。

现在可以理解ctrl+s和yarn run lint --fix方案为何可以修复eslint错误了吧,因为Git自动将CRLF转换成了LF。

最佳实践:

现在VScode,Notepad++编辑器都能够自动识别文件的换行符是LF还是CRLF。

如果你用的是windows,文件编码是UTF-8且包含中文,最好全局将autocrlf设置为false。

git config --global core.autocrlf false

注意:git全局配置之后,你需要重新拉取代码。

总结

查找了不少资料,stackoverflow、github上对这个问题有相应的讨论和解决办法,但都不能触及灵魂。作下此文,以便日后翻阅,若对遇坑的朋友有所帮助,笔者乐此不疲!

原文:
Delete eslint(prettier/prettier) 错误的解决方案 错误的解决方案")

linq 从字符串转换日期和/或时间时,转换失败

一、测试

在执行一句简单的 efcore linq 查询时,如下:

    var q = from a in _db.TeamPlans
            where a.FromDate >= DateTime.Parse("2021-6-9 11:10:42")
            select a;

    var list = q.ToList();

Microsoft.Data.SqlClient.SqlException:“从字符串转换日期和/或时间时,转换失败。”(一脸黑头问号)

这不是最常见的写法吗?为什么会报错呢?
于是我换成了如下方式

    var q = from a in _db.TeamPlans
            select a;
    q = q.Where(a => a.FromDate >= DateTime.Parse("2021-6-9 11:10:42"));
    var list = q.ToList();

此时却没有问题,可以正常运行,这是为什么呢?

二、分析

仔细看上面的异常为:Microsoft.Data.SqlClient.SqlException,其中SqlClient格外显眼,说明这个异常时数据库抛出来的,于是咱们进行下面的分析

查看通过linq to sql生成的sql语句如下:

SELECT // 省略字段
FROM [TeamPlans] AS [t]
WHERE [t].[FromDate] >= '2021-06-09T11:10:42.0000000'

然后再查看上面第二种查询方式生成的sql如下:

SELECT // 省略字段
FROM [TeamPlans] AS [t]
WHERE [t].[FromDate] >= '2021-06-09T11:10:42.000'

仔细看最后的时间,好家伙,第一种方式生成sql语句精度远远大于sqlserver能识别的范围

三、解决

那么知道问题的原因出在“CLR和SQLServer数据类型精度不一致”的问题上,那么我们得找一个可以映射CLR和SQLServer数据类型的方法对吧
于是想到了ColumnAttribute特性,该特性中恰好有个TypeName属性,那么我们试试看。

在需要处理的DateTime类型的字段上添加一个Column特性即可解决该问题,
添加Column特性后,生成的sql中会自动降低该DateTime类型的精度

    [Column(TypeName = "datetime")]
    public DateTime FromDate { get; set; }

这些常见的特性都在System.ComponentModel.DataAnnotations.Schema命名空间下

参考
EF Core 查看linq to sql生成的sql
Linq to SQL 中datetime 和string转换

.Net 反射 方法 用于调用泛型方法

核心代码:

  1. 通过Type下的GetMethod获取对应的方法信息
  2. 在通过MethodInfo下的MakeGenericMethod设置泛型参数
  3. 最后通过Invoke进行触发

注意:被反射的方法需要为public访问权限

        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable)makeGenericMethod.Invoke(context, null);

例一:

反射DbContext类中的DbContext.Set方法

public static partial class CustomExtensions
{
    /// <summary>
    /// 通过指定实体类型名反射获取 IQueryable
    /// </summary>
    /// <param name="context"></param>
    /// <param name="entityName"></param>
    /// <returns></returns>
    public static IQueryable GetDbSet(this DbContext context, string entityName)
    {
        IEntityType findEntityType = context.Model.FindEntityType(entityName);
        return context.GetDbSet(findEntityType.ClrType);
    }
    /// <summary>
    /// 通过指定实体类型反射获取 IQueryable
    /// </summary>
    /// <param name="context"></param>
    /// <param name="entityType"></param>
    /// <returns></returns>
    public static IQueryable GetDbSet(this DbContext context, Type entityType)
    {
        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable)makeGenericMethod.Invoke(context, null);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="Type"></typeparam>
    /// <param name="context"></param>
    /// <param name="entityType"></param>
    /// <returns></returns>
    public static IQueryable<T> GetDbSet<T>(this DbContext context, Type entityType)
    {
        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable<T>)makeGenericMethod.Invoke(context, null);
    }
}

例二:

反射DbHelper类中的CheckDataBuild方法

    /// <summary>
    /// 校验标识了CheckDataAttribute的字段,所对应的外键数据是否存在
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="errorMsgList"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    public static bool CheckData<T>(T item, List<string> errorMsgList, DatabaseContext db) where T : class
    {
        var t = typeof(T);
        var props = t.GetProperties();
        var success = true;
        var methodInfo = typeof(DbHelper).GetMethod(nameof(CheckDataBuild));

        foreach (var prop in props)
        {
            var attr = prop.GetCustomAttributes().OfType<CheckDataAttribute>().FirstOrDefault();
            if (attr != null)
            {
                var val = prop.GetValue(item);

                MethodInfo mi = methodInfo.MakeGenericMethod(attr.EntityType);
                success = (bool)mi.Invoke(null, new object[] { errorMsgList, db, success, prop, attr, val });
            }
        }
        return success;
    }
    /// <summary>
    /// 数据校验
    /// </summary>
    /// <typeparam name="T2"></typeparam>
    /// <param name="errorMsgList">错误信息</param>
    /// <param name="db">db对象</param>
    /// <param name="success">校验结果</param>
    /// <param name="prop">属性信息</param>
    /// <param name="attr">校验的特性</param>
    /// <param name="val">值</param>
    /// <returns></returns>
    public static bool CheckDataBuild<T2>(List<string> errorMsgList, DatabaseContext db, bool success, PropertyInfo prop, CheckDataAttribute attr, object val) where T2 : class
    {
        var table = db.GetDbSet<T2>(attr.EntityType);
        var existedItem = table.QueryByField(attr.FieldName, val, attr.EntityType);

        if (existedItem == null)
        {
            success = false;
            errorMsgList.Add($"Value {val} for filed {prop.Name} not available");
        }
        return success;
    }

参考

C#里Type 如何转换成泛型T? - 知乎
C# Type传参转换成泛型T_雷小虎的专栏-CSDN博客