You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Maui中LiteDB存储SkiaSharp数据加载遇InvalidCastException求助

问题诊断

你遇到的System.InvalidCastException根源是LiteDB默认无法正确序列化/反序列化SkiaSharp的非POCO类型:

  • SKPoint是值类型(struct),如果数据库中对应字段因序列化问题存为null,反序列化时无法将null转换为值类型,直接抛出异常。
  • SKPaint是SkiaSharp的复杂对象,包含大量内部状态,LiteDB的默认序列化器无法完整处理它的所有属性,导致部分数据丢失或存储为null。
解决方案

下面提供两种可行的解决思路,推荐第二种(中间POCO类),更易维护和控制存储内容。

方案1:自定义LiteDB Bson映射

为SKPoint和SKPaint实现自定义序列化/反序列化逻辑,告诉LiteDB如何处理这些类型:

1. 注册自定义序列化器

在应用启动时(比如MauiProgram.cs的CreateMauiApp方法中)添加以下代码:

using LiteDB;
using SkiaSharp;

// 为SKPoint注册自定义序列化器
BsonMapper.Global.RegisterType(
    (SKPoint point) => new BsonDocument { ["X"] = point.X, ["Y"] = point.Y },
    (bson) => new SKPoint(bson["X"].AsSingle, bson["Y"].AsSingle)
);

// 为SKPaint注册自定义序列化器(只存储绘图必需的属性,按需添加)
BsonMapper.Global.RegisterType(
    (SKPaint paint) => new BsonDocument
    {
        ["Color"] = paint.Color.ToArgb(),
        ["StrokeWidth"] = paint.StrokeWidth,
        ["Style"] = paint.Style.ToString(),
        ["IsAntialias"] = paint.IsAntialias
    },
    (bson) => new SKPaint
    {
        Color = new SKColor((int)bson["Color"].AsInt32),
        StrokeWidth = bson["StrokeWidth"].AsSingle,
        Style = Enum.Parse<SKPaintStyle>(bson["Style"].AsString),
        IsAntialias = bson["IsAntialias"].AsBoolean
    }
);

2. 重新测试数据读写

注册完成后,LiteDB会使用自定义逻辑处理SKPoint和SKPaint,避免值类型null转换问题,同时只存储必要的绘图属性。

方案2:使用中间POCO类(推荐)

创建与Stroke对应的普通CLR对象(POCO),将SkiaSharp对象转换为POCO后存储,读取时再转换回SkiaSharp对象:

1. 创建可序列化的POCO类

// 用于存储SKPoint的POCO
public class StrokePoint
{
    public float X { get; set; }
    public float Y { get; set; }

    // 转换方法
    public static StrokePoint FromSKPoint(SKPoint point) => new StrokePoint { X = point.X, Y = point.Y };
    public SKPoint ToSKPoint() => new SKPoint(X, Y);
}

// 用于存储SKPaint核心属性的POCO
public class StrokePaint
{
    public int ColorArgb { get; set; }
    public float StrokeWidth { get; set; }
    public string Style { get; set; }
    public bool IsAntialias { get; set; }

    // 转换方法
    public static StrokePaint FromSKPaint(SKPaint paint) => new StrokePaint
    {
        ColorArgb = paint.Color.ToArgb(),
        StrokeWidth = paint.StrokeWidth,
        Style = paint.Style.ToString(),
        IsAntialias = paint.IsAntialias
    };
    public SKPaint ToSKPaint() => new SKPaint
    {
        Color = new SKColor(ColorArgb),
        StrokeWidth = StrokeWidth,
        Style = Enum.Parse<SKPaintStyle>(Style),
        IsAntialias = IsAntialias
    };
}

// 可序列化的Stroke POCO
public class StrokeDto
{
    public StrokePaint Paint { get; set; }
    public List<StrokePoint> Points { get; set; } = new List<StrokePoint>();

    // 与原Stroke类的转换
    public static StrokeDto FromStroke(Stroke stroke) => new StrokeDto
    {
        Paint = StrokePaint.FromSKPaint(stroke.Paint),
        Points = stroke.Points.Select(StrokePoint.FromSKPoint).ToList()
    };
    public Stroke ToStroke() => new Stroke
    {
        Paint = Paint.ToSKPaint(),
        Points = Points.Select(p => p.ToSKPoint()).ToList()
    };
}

// 修改NSketch类,存储Dto而非原Stroke
public class NSketch: NotebookItem
{
    public List<StrokeDto> StrokeDtos { get; set; }

    [BsonIgnore]
    // 提供原Strokes的访问器,自动转换Dto
    public List<Stroke> Strokes
    {
        get => StrokeDtos?.Select(dto => dto.ToStroke()).ToList() ?? new List<Stroke>();
        set => StrokeDtos = value?.Select(StrokeDto.FromStroke).ToList() ?? new List<StrokeDto>();
    }

    [BsonIgnore]
    public override string Description => "Skizze";

    public NSketch()
    {
        StrokeDtos = new List<StrokeDto>();
    }       
}

2. 数据读写逻辑无需修改

此时NSketch类的StrokeDtos是完全可序列化的POCO,LiteDB可以正常存储和读取,而Strokes属性通过BsonIgnore标记,不会被LiteDB处理,仅作为业务代码的访问入口自动转换。

验证

两种方案都可以解决InvalidCastException问题,方案2的优势在于:

  • 完全控制存储的数据内容,避免存储不必要的SkiaSharp内部状态
  • 代码更清晰,后续维护和扩展更方便

内容的提问来源于stack exchange,提问作者Expert1982

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:33:29