Xamarin Forms中Firebase Realtime Database订阅无增改却触发
问题描述
在Xamarin Forms中使用Firebase Realtime Database时遇到订阅异常:数据提交功能正常,但订阅Insert/Update变更事件时,未对数据库执行插入或更新操作,订阅事件就会触发。以下是MVVM代码及Get方法实现,请问为何会出现这种情况?是否采用了错误的实现模式?
MVVM代码
public ICommand SaveCommand { get; private set; } public ICommand LoadContentsCommand { get; private set; } private readonly App CurrentApp; private readonly IFirebaseDatabaseService firebaseDatabaseService; public ICommand SaveCommand { get; private set; } public ICommand LoadContentsCommand { get; private set; } private readonly App CurrentApp; private readonly IFirebaseDatabaseService firebaseDatabaseService; public CommunityHomePageViewModel() { firebaseDatabaseService = DependencyService.Get<IFirebaseDatabaseService>(); Title = "Community"; LoadContentsCommand = new Command(async () => await LoadContentsAsync()); SaveCommand = new Command(async () => await SaveDataAsync()); LoadContentsCommand.Execute(null); CurrentApp = Application.Current as App; SubscribeToChanges(); } ObservableCollection<Post> _items; public ObservableCollection<Post> Items { get { return _items; } set { SetProperty(ref _items, value); } } async Task SaveDataAsync() { try { await firebaseDatabaseService.PostAsync(new Post() { Comment = $"Random comment {new Random().Next(0, 100)}", UserKey = CurrentApp.CurrentUser.Id }); } catch (Exception e) { Logger.LogException(e); } } async Task LoadContentsAsync() { try { var items = await firebaseDatabaseService.GetItemsAsync(string.Empty); Items = new ObservableCollection<Post>(items); } catch (Exception e) { Logger.LogException(e); } } // subscribe occurs just after the items are loaded without waiting for the insert/save to occur. void SubscribeToChanges() { firebaseDatabaseService.Client().Child(nameof(Post)) .AsObservable<Post>() .Where(post => !Items.Contains(post.Object) && post.EventType == Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate) .Subscribe(obs => { Console.WriteLine($"{obs.Key}"); var post = obs.Object; switch (obs.EventType) { case Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate: Items.Insert(0, post); break; } }); }
Get方法代码
async Task<IEnumerable<Post>> IFirebaseDatabaseService.GetItemsAsync(string id) { if (!_firebaseAuth.IsSignedIn) return null; if (!string.IsNullOrEmpty(id)) { return (await _firebaseClient .Child(typeof(Post).Name) .OrderByKey() .StartAt(id) .LimitToFirst(10) .OnceAsync<Post>()) .Select(i => new Post { Comment = i.Object.Comment, UserKey = i.Object.UserKey }); } return (await _firebaseClient .Child(typeof(Post).Name) .OrderByKey() .LimitToFirst(10) .OnceAsync<Post>()) .Select(i => new Post { Comment = i.Object.Comment, UserKey = i.Object.UserKey }); }
问题原因
- Firebase订阅的初始快照机制:
AsObservable<T>订阅时会立即返回当前节点的所有已有数据,触发InsertOrUpdate事件——这是Firebase实时流式订阅的默认行为,先同步现有数据,再监听后续变更。 - 对象引用比较失效:
GetItemsAsync中通过Select创建了全新的Post实例,而订阅返回的post.Object是Firebase反序列化的新实例。由于Post未重写Equals和GetHashCode,Items.Contains用引用比较判断,导致已有数据被误判为“不存在”,触发插入逻辑。 - 异步加载与订阅时机冲突:构造函数中先执行异步的
LoadContentsCommand,紧接着调用SubscribeToChanges。数据加载完成前订阅就已生效,此时Items未赋值,!Items.Contains(post.Object)始终为true,所有初始数据都会被重复插入。
修复方案
1. 重写Post类的Equals和GetHashCode
让Contains能通过内容(或唯一Key)判断对象相等:
public class Post { public string Comment { get; set; } public string UserKey { get; set; } public string Key { get; set; } // 新增Firebase节点Key属性 public override bool Equals(object obj) { if (obj is Post other) { return string.Equals(Key, other.Key); // 用节点Key作为唯一判断依据 } return false; } public override int GetHashCode() { return Key?.GetHashCode() ?? base.GetHashCode(); } }
2. 调整订阅时机,等数据加载完成后再订阅
修改构造函数,确保数据加载完再启动订阅:
public CommunityHomePageViewModel() { firebaseDatabaseService = DependencyService.Get<IFirebaseDatabaseService>(); Title = "Community"; LoadContentsCommand = new Command(async () => { await LoadContentsAsync(); // 数据加载完成后再订阅变更 SubscribeToChanges(); }); SaveCommand = new Command(async () => await SaveDataAsync()); CurrentApp = Application.Current as App; LoadContentsCommand.Execute(null); }
3. 优化GetItemsAsync,保留Firebase节点Key
获取数据时把节点Key赋值给Post,方便后续判断:
async Task<IEnumerable<Post>> IFirebaseDatabaseService.GetItemsAsync(string id) { if (!_firebaseAuth.IsSignedIn) return null; var query = _firebaseClient.Child(typeof(Post).Name).OrderByKey(); if (!string.IsNullOrEmpty(id)) { query = query.StartAt(id).LimitToFirst(10); } else { query = query.LimitToFirst(10); } return (await query.OnceAsync<Post>()) .Select(i => new Post { Key = i.Key, // 保存Firebase节点Key Comment = i.Object.Comment, UserKey = i.Object.UserKey }); }
4. 优化订阅逻辑,过滤初始数据(可选)
如果只需要监听后续变更,可通过记录已加载Key过滤初始快照:
void SubscribeToChanges() { var existingKeys = Items.Select(p => p.Key).ToHashSet(); firebaseDatabaseService.Client().Child(nameof(Post)) .AsObservable<Post>() .Where(obs => obs.EventType == Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate && !existingKeys.Contains(obs.Key)) .Subscribe(obs => { Console.WriteLine($"{obs.Key}"); var post = obs.Object; post.Key = obs.Key; Items.Insert(0, post); existingKeys.Add(obs.Key); }); }
内容的提问来源于stack exchange,提问作者ARH
相关产品推荐
相关产品推荐

