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

.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 &#10;.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="&#9660;" 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 10:05:54