Xamarin.Android中SQLite模板数据去重与内存异常解决咨询
为什么Unique属性没生效?
你遇到的问题根源在于:SQLite-Net(包括Extensions)的Unique属性是字段级约束,而你试图用它约束整个TemplateImages列表的唯一性——这是业务逻辑层面的组合唯一性,数据库本身无法直接通过字段约束实现(更别说你加在OneToMany导航属性上,这个属性不会对应数据库中的实际字段,所以Unique完全不起作用)。另外,TemplateImage的自增Id本身就是唯一的,这也导致单条TemplateImage记录永远不会重复,但这和你要的“图片列表整体唯一”完全是两回事。
正确的唯一性实现方案
要保证“不存在顺序相同、图片完全一致的TemplateImages列表”,最佳方式是给Template添加一个基于图片列表的唯一哈希值,并给这个哈希字段加上数据库级的Unique约束。具体步骤如下:
1. 调整数据库模型
修改实体类,用图片内容的MD5代替ResourceID,同时新增TemplateImagesHash字段来存储整个图片列表的哈希:
[Table("Templates")] public class Template { [PrimaryKey, AutoIncrement] public int Id { get; set; } public int Category { get; set; } [OneToMany(CascadeOperations = CascadeOperation.All)] // 级联操作方便管理子项 public List<TemplateImage> TemplateImages { get; set; } [Unique] // 关键:给列表哈希加唯一约束 public string TemplateImagesHash { get; set; } // 存储整个图片列表的哈希 } [Table("TemplateImages")] public class TemplateImage { [PrimaryKey, AutoIncrement] public int Id { get; set; } public int Category { get; set; } public string ImageMd5 { get; set; } // 存储图片内容的MD5,代替易变的ResourceID [ForeignKey(typeof(Template))] public int TemplateId { get; set; } }
2. 优化图片MD5生成(解决内存溢出)
你之前把矢量图转Bitmap再生成MD5的方式很容易导致内存溢出——Bitmap会占用大量内存,尤其是高分辨率图片。正确的做法是直接读取资源的原始内容来计算MD5:
- 对于矢量XML资源(比如
drawable里的.xml文件):直接读取XML的原始字节流 - 对于位图资源(
.png/.jpg):同样读取原始文件流
实现一个通用的MD5计算方法:
public static string CalculateMd5(Stream stream) { using var md5 = MD5.Create(); var hashBytes = md5.ComputeHash(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } // 获取图片资源的MD5 public string GetImageResourceMd5(Resources res, int resId) { using var stream = res.OpenRawResource(resId); return CalculateMd5(stream); }
这个方法直接操作资源流,不需要加载Bitmap,内存占用极低,从根源解决内存溢出问题。
3. 生成图片列表的唯一哈希
要保证列表的顺序和内容都唯一,需要把所有图片的ImageMd5按顺序拼接(用特殊分隔符避免不同组合产生相同字符串),再对拼接后的字符串计算MD5:
public string CalculateTemplateImagesHash(List<TemplateImage> images) { // 按顺序拼接每个图片的MD5,用特殊分隔符区分 var combined = string.Join("|", images.Select(img => img.ImageMd5)); using var md5 = MD5.Create(); var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(combined)); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); }
4. 实现AddTemplate的唯一性校验
在添加Template前,先计算所有图片的MD5,生成列表哈希,再查询数据库是否存在相同哈希的记录:
public async Task<bool> AddTemplateAsync(Resources res, int category, List<int> resourceIds) { // 1. 生成每个图片的TemplateImage对象 var templateImages = resourceIds.Select(resId => new TemplateImage { Category = category, ImageMd5 = GetImageResourceMd5(res, resId) }).ToList(); // 2. 计算列表哈希 var imagesHash = CalculateTemplateImagesHash(templateImages); // 3. 检查数据库是否已有相同哈希的Template var existingTemplate = await _connection.Table<Template>() .FirstOrDefaultAsync(t => t.TemplateImagesHash == imagesHash); if (existingTemplate != null) { // 已存在,不重复添加 return false; } // 4. 插入新Template(级联插入TemplateImages) var newTemplate = new Template { Category = category, TemplateImages = templateImages, TemplateImagesHash = imagesHash }; await _connection.InsertWithChildrenAsync(newTemplate, true); return true; }
额外注意事项
- 确保使用
SQLite-Net Extensions的InsertWithChildrenAsync方法来插入级联数据,这样TemplateId会自动赋值给TemplateImage。 - 如果你的图片资源会更新,要注意:如果图片内容变化,其MD5也会变化,此时会被视为新的图片列表。
- 如果你需要支持“图片相同但顺序不同算同一个列表”的场景,只需在拼接哈希前对
ImageMd5列表进行排序即可。
内容的提问来源于stack exchange,提问作者CDrosos

