.NET MAUI页面预加载作者数据失败问题排查
.NET MAUI CollectionView 无法加载作者数据问题
我知道这类问题已经被问过很多次,但试了各种方案都没用。现在我想在.NET MAUI里把作者数据加载到CollectionView中,选作者查看详情,但卡在预加载数据这一步。Library类加载作者信息是正常的(GetAuthors任务里的Debug能输出正确内容)。现在运行应用直接没反应,注释掉MainViewModel构造函数里的Debug语句,应用能启动但CollectionView里没数据。试了好几种异步加载数据的方法,结果都一样。怀疑是不是{Binding FullName}绑定错了,或者还有其他没考虑到的地方?
相关代码
MainPage.xaml
<?xml version = "1.0" encoding = "UTF-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:local="clr-namespace:Combox.ViewModels" xmlns:library ="clr-namespace:Library" x:Class="Combox.MainPage" x:Name="mainView"> <ContentPage.BindingContext> <local:MainViewModel /> </ContentPage.BindingContext> <ContentPage.Behaviors> <toolkit:EventToCommandBehavior EventName="Appearing" Command="{Binding GetAuthorsCommand}" /> </ContentPage.Behaviors> <ScrollView> <VerticalStackLayout Padding="30,0" Spacing="25"> <Image Source="dotnet_bot.png" HeightRequest="185" Aspect="AspectFit" SemanticProperties.Description="dot net bot in a race car number eight" /> <Label Text="Hello, World!" Style="{StaticResource Headline}" SemanticProperties.HeadingLevel="Level1" /> <Label Text="Welcome to .NET Multi-platform App UI" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2" SemanticProperties.Description="Welcome to dot net Multi platform App U I" /> <toolkit:Expander IsExpanded="{Binding IsOpened}"> <toolkit:Expander.Header> <HorizontalStackLayout MaximumHeightRequest="42" Padding="10"> <Label HorizontalOptions="Start" Text="{Binding SelectedText}" /> <Label Text="▼" HorizontalOptions="EndAndExpand" VerticalOptions="Center" /> </HorizontalStackLayout> </toolkit:Expander.Header> <CollectionView ItemsSource="{Binding DataList}" HeightRequest="190" SelectionMode="Single" SelectedItem="{Binding SelectedItem}" SelectionChangedCommand="{Binding SelectionCommand}"> <CollectionView.ItemTemplate> <DataTemplate> <Label Text="{Binding FullName}" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </toolkit:Expander> <Label Text="End of the page" /> </VerticalStackLayout> </ScrollView> </ContentPage>
MainViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Library; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows.Input; namespace Combox.ViewModels { public partial class MainViewModel : ObservableObject { string _selectedText = "Select the item"; public string SelectedText { get { return _selectedText; } set { SetProperty(ref _selectedText, value); } } public Author? SelectedItem { get; set; } bool _isOpened = false; public bool IsOpened { get { return _isOpened; } private set { SetProperty(ref _isOpened, value); } } ObservableCollection<Author>? _dataList; public ObservableCollection<Author>? DataList { get { return _dataList; } private set { _dataList = value; } } public ICommand SelectionCommand { private set; get; } public AuthorService? AuthorService { get; private set; } LibraryFunctions? LibraryFunctions { get; set; } public MainViewModel() { AuthorService = new AuthorService(); LibraryFunctions = new LibraryFunctions(); AuthorService.authorsCollection = LibraryFunctions.authorsCollection; SelectedItem = new Author(); SelectionCommand = new Command( execute: () => { IsOpened = false; Author temp = SelectedItem; SelectedText = SelectedItem.FullName!; Debug.Write($"chosen {temp.FullName}"); RefreshCanExecutes(); }, canExecute: () => { IsOpened = true; return true; } ); Debug.WriteLine($"author {DataList![20].FullName}"); } void RefreshCanExecutes() { ((Command)SelectionCommand).ChangeCanExecute(); } [RelayCommand] private async Task GetAuthors() { List<Author> source = new List<Author>(); source = await AuthorService!.getAuthors("last"); if (source == null || source.Count == 0) { Debug.Write("no authors"); throw new Exception("no authors"); } this.DataList = new ObservableCollection<Author>(source)!; Debug.WriteLine($"author {DataList![20].FullName}"); } } }
SmallLibrary.cs
using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using MongoDB.Driver.Linq; using System.Diagnostics; namespace Library { [BsonIgnoreExtraElements] public class Author { [BsonId][BsonRepresentation(BsonType.ObjectId)] public ObjectId _id { get; set; } [BsonElement("first")] public string? first { get; set; } [BsonElement("last")] public string? last { get; set; } [BsonElement("author_id")] public BsonInt32? author_id { get; set; } [BsonElement("FullName")][BsonIgnoreIfNull] public string? FullName { get; set; } } [BsonIgnoreExtraElements] public class Book { [BsonId][BsonRepresentation(BsonType.ObjectId)] public ObjectId _id { get; set; } [BsonElement("title")] public string? title { get; set; } [BsonElement("title_id")] public BsonInt32? title_id { get; set; } [BsonElement("author")] public string? author { get; set; } [BsonElement("issue_date")] public string? issue_date { get; set; } [BsonElement("return_date")] public string? return_date { get; set; } } public class AuthorService { public IMongoCollection<Author>? authorsCollection; public async Task<List<Author>?> getAuthors(string order = "first") { IMongoQueryable<Author> results = from author in this.authorsCollection.AsQueryable() orderby author.last, author.first select author; List<Author>? authors = await results.ToListAsync(); if (authors == null) { return null; } else { foreach (Author author in authors) { if (order == "first") { author.FullName = author.first + " " + author.last; } else if (order == "last") { author.FullName = author.last + ", " + author.first; } else { throw new Exception($"Unknown order {order}"); } } authors!.Insert(0, new Author() { FullName = "" }); return authors; } } public async Task<string> GetAuthorName(string _id) { ObjectId makeId = new ObjectId(_id); IMongoQueryable<Author> results = from author in this.authorsCollection.AsQueryable() where author._id == makeId select author; List<Author> authorList = await results.ToListAsync(); string name = ""; foreach (Author author in authorList) { name = author.first + " " + author.last; } return name; } public async Task<string?> GetAuthorId(string? first, string? last) { IMongoQueryable<Author> results = from a in authorsCollection.AsQueryable() where a.first == first && a.last == last select a; Author? author = await results.FirstOrDefaultAsync(); if (author == null) { return null; } else { return author._id.ToString(); } } } public class BookService { public MongoCollectionBase<Book>? booksCollection; public async Task<List<Book>> getRecent(AuthorService authorService) { IMongoQueryable<Book> results = (from book in booksCollection.AsQueryable() orderby book.return_date descending, book.title ascending select book).Take(10); List<Book> results1 = await results.ToListAsync(); return results1; } public async Task<List<Book>> getBooksForAuthor(string author_id) { IMongoQueryable<Book> results = from book in booksCollection.AsQueryable() where book.author == author_id orderby book.title select book; return await results.ToListAsync(); } public async Task<List<Book>?> GetBookDetails(string? title, string? authorId) { IMongoQueryable<Book>? results = from book in booksCollection.AsQueryable() where book.title == title && book.author == authorId select book; return await results.ToListAsync(); } } public class LibraryFunctions { private MongoClient mongoClient; public MongoCollectionBase<Author> authorsCollection { get; set; } public MongoCollectionBase<Book> booksCollection { get; set; } public MongoDatabaseBase database { get; set; } public BookService BookService { get; set; } public AuthorService AuthorService { get; set; } public string mongodb { get; set; } public LibraryFunctions() { mongodb = "my mongo login details"; MongoClientSettings settings = MongoClientSettings.FromConnectionString(mongodb); settings.LinqProvider = LinqProvider.V3; mongoClient = new MongoClient(settings); if (mongoClient == null) { Debug.WriteLine("Connection to MongoDB failed"); throw new Exception("Connection to Atlas failed"); } this.database = (MongoDatabaseBase)mongoClient.GetDatabase("library"); if (database == null) { Debug.WriteLine("Connection to database failed"); throw new Exception("Failed to connect to database"); } this.authorsCollection = (MongoCollectionBase<Author>)database.GetCollection<Author>("authors"); this.booksCollection = (MongoCollectionBase<Book>)database.GetCollection<Book>("book_titles"); this.BookService = new BookService(); BookService.booksCollection = this.booksCollection; this.AuthorService = new AuthorService(); AuthorService.authorsCollection = this.authorsCollection; } public async Task<List<Book>> _getBooksForAuthor(string author_id) { return await this.BookService.getBooksForAuthor(author_id); } } }
问题分析与解决方案
1. DataList 属性未触发UI更新
DataList的setter只是直接赋值,没有调用SetProperty,导致UI无法感知数据变化。修改如下:
ObservableCollection<Author>? _dataList; public ObservableCollection<Author>? DataList { get { return _dataList; } private set { SetProperty(ref _dataList, value); } }
2. 构造函数访问未初始化的DataList
构造函数里的Debug.WriteLine($"author {DataList![20].FullName}");会直接抛出空引用异常,导致应用启动失败。直接删除该行,或者移到GetAuthors方法执行后。
3. SelectedItem 属性未实现变更通知
SelectedItem是普通属性,没有用SetProperty,可能导致绑定失效。修改为:
Author? _selectedItem; public Author? SelectedItem { get { return _selectedItem; } set { SetProperty(ref _selectedItem, value); } }
同时构造函数里的初始化改为:
_selectedItem = new Author();
4. GetAuthorsCommand 访问权限问题
虽然CommunityToolkit.Mvvm会自动为private的[RelayCommand]方法生成public命令,但显式改为public更稳妥:
[RelayCommand] public async Task GetAuthors() { // 原有代码不变 }
额外调试建议
- 开启MAUI绑定调试:在
App.xaml.cs的Build方法中添加ConfigureBindingDebugging(true),查看输出窗口的绑定错误日志。 - 检查
Author类的FullName赋值逻辑:如果first或last为null,拼接后会得到null或空字符串,可添加空值判断避免异常。
内容的提问来源于stack exchange,提问作者user3017691
相关产品推荐
相关产品推荐

