我一直在构建一个应用程序,它使用 LoadOperation 的实体返回 IEnumerable,该 IEnumerable 成为我的视图模型中 CollectionViewSource 的源。我现在发现这种方法的潜在陷阱,在我的 Silverlight 客户端中添加实体时,我看不到这些实体,除非我提交新实体,然后重新加载,或维护我绑定到的单独的项目集合。

我真正看到的选择是:

  1. 添加一个 ObservableCollection 用作 ViewModel 中 CollectionViewSource 属性的源 - 这样我可以同时添加到 DomainContext 和 ObservableCollection 以保持集合同步。
  2. 直接将 Binding 更改为 EntitySet,并添加过滤事件处理程序以提供对 CollectionViewSource 的过滤。

如果有人对每种方法的优缺点有建议或想法,我将不胜感激。我特别想知道,性能和/或编程优势是否有利于其中之一?

有帮助吗?

解决方案

我每次都采用这种方法。首先,我将展示一个讨论这个问题的参考点,然后我将强调支持每种方法所需的不同变化。

我的演示的基础是单个经过身份验证的域服务,它返回单个资源实体。我将公开 4 个命令(保存、撤消、添加和删除),以及一个集合和一个用于保存 SelectedResource 的属性。

2 个不同的类实现此接口(1 个用于混合,1 个用于生产)。制作是我在这里讨论的唯一一个。请注意 GetMyResources 函数中的操作(lo.Entities):

public class WorkProvider
{
    static WorkContext workContext;
    public WorkProvider()
    {
        if (workContext == null)
            workContext = new WorkContext();
    }
    public void AddResource(Resource resource)
    {
        workContext.Resources.Add(resource);
    }
    public void DelResource(Resource resource)
    {
        workContext.Resources.Remove(resource);
    }
    public void UndoChanges()
    {
        workContext.RejectChanges();
    }
    public void SaveChanges(Action action)
    {
        workContext.SubmitChanges(so =>
            {
                if (so.HasError)
                    // Handle Error
                    throw so.Error;
                else
                    action();
            }, null);
    }
    public void GetMyResources(Action<IEnumerable<Resource>> action)
    {
        var query = workContext.GetResourcesQuery()
            .Where(r => r.UserName == WebContext.Current.User.Name);
        workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
            {
                if (lo.HasError)
                    // Handle Error
                    throw lo.Error;
                else
                    action(lo.Entities);
            }, null);
    }
}

在 ViewModel 中,我有以下实现:

public class HomeViewModel : ViewModelBase
{
    WorkProvider workProvider;
    public HomeViewModel()
    {
        workProvider = new WorkProvider();
    }

    // _Source is required when returning IEnumerable<T>
    ObservableCollection<Resource> _Source; 
    public CollectionViewSource Resources { get; private set; }
    void setupCollections()
    {
        Resources = new CollectionViewSource();
        using (Resources.DeferRefresh())
        {
            _Source = new ObservableCollection<Resource>();
            Resources.Source = _Source;
            Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
            Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
            Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
        }
    }
    void loadMyResources()
    {
        workProvider.GetMyResources(results =>
            {
                using (Resources.DeferRefresh())
                {
                    // This is required when returning IEnumerable<T>
                    _Source.Clear();
                    foreach (var result in results)
                    {
                        if (!_Source.Contains(result))
                            _Source.Add(result);
                    }
                }
            });
    }
    Resource _SelectedResource;
    public Resource SelectedResource
    {
        get { return _SelectedResource; }
        set
        {
            if (_SelectedResource != value)
            {
                _SelectedResource = value;
                RaisePropertyChanged("SelectedResource");
            }
        }
    }

    public RelayCommand CmdSave { get; private set; }
    public RelayCommand CmdUndo { get; private set; }
    public RelayCommand CmdAdd { get; private set; }
    public RelayCommand CmdDelete { get; private set; }
    void setupCommands()
    {
        CmdSave = new RelayCommand(() =>
            {
                workProvider.SaveChanges(() =>
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(() =>
                            {
                                System.Windows.MessageBox.Show("Saved");
                            });
                    });
            });
        CmdUndo = new RelayCommand(() =>
            {
                workProvider.UndoChanges();
                // This is required when returning IEnumerable<T>
                loadMyResources();
            });
        CmdAdd = new RelayCommand(() =>
            {
                Resource newResource = new Resource()
                {
                    ResourceID = Guid.NewGuid(),
                    Rate = 125,
                    Title = "Staff",
                    UserName = "jsmith"
                };
                // This is required when returning IEnumerable<T>
                _Source.Add(newResource);
                workProvider.AddResource(newResource);
            });
        CmdDelete = new RelayCommand(() =>
        {
            // This is required when returning IEnumerable<T>
            _Source.Remove(_SelectedResource);
            workProvider.DelResource(_SelectedResource);
        });
    }
}

替代方法将涉及更改 WorkProvider 类,如下所示(请注意返回的操作(workContext.Resources):

    public void GetMyResources(Action<IEnumerable<Resource>> action)
    {
        var query = workContext.GetResourcesQuery()
            .Where(r => r.UserName == WebContext.Current.User.Name);
        workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
            {
                if (lo.HasError)
                    // Handle Error
                    throw lo.Error;
                else
                    // Notice Changed Enumeration
                    action(workContext.Resources);
            }, null);
    }

视图模型的更改如下(注意删除了 _Source ObservableCollection):

public class HomeViewModel : ViewModelBase
{
    WorkProvider workProvider;
    public HomeViewModel()
    {
        workProvider = new WorkProvider();
    }

    public CollectionViewSource Resources { get; private set; }
    void setupCollections()
    {
        Resources = new CollectionViewSource();
        using (Resources.DeferRefresh())
        {
            Resources.Filter += (s,a) =>
                {
                    a.Accepted = false;
                    if (s is Resource)
                    {
                        Resource res = s as Resource;
                        if (res.UserName == WebContext.Current.User.Name)
                            a.Accepted = true;
                    }
                };
            Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
            Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
            Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
        }
    }
    void loadMyResources()
    {
        workProvider.GetMyResources(results =>
            {
                using (Resources.DeferRefresh())
                {
                    Resources.Source = results;
                }
            });
    }
    Resource _SelectedResource;
    public Resource SelectedResource
    {
        get { return _SelectedResource; }
        set
        {
            if (_SelectedResource != value)
            {
                _SelectedResource = value;
                RaisePropertyChanged("SelectedResource");
            }
        }
    }

    public RelayCommand CmdSave { get; private set; }
    public RelayCommand CmdUndo { get; private set; }
    public RelayCommand CmdAdd { get; private set; }
    public RelayCommand CmdDelete { get; private set; }
    void setupCommands()
    {
        CmdSave = new RelayCommand(() =>
            {
                workProvider.SaveChanges(() =>
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(() =>
                            {
                                System.Windows.MessageBox.Show("Saved");
                            });
                    });
            });
        CmdUndo = new RelayCommand(() =>
            {
                workProvider.UndoChanges();
                Resources.View.Refresh();
            });
        CmdAdd = new RelayCommand(() =>
            {
                Resource newResource = new Resource()
                {
                    ResourceID = Guid.NewGuid(),
                    Rate = 125,
                    Title = "Staff",
                    UserName = "jsmith"
                };
                workProvider.AddResource(newResource);
            });
        CmdDelete = new RelayCommand(() =>
        {
            workProvider.DelResource(_SelectedResource);
        });
    }
}

虽然第二种方法肯定需要在 CollectionViewSource 的配置中添加过滤器事件处理程序,并且可以被视为过滤数据两次(一次在服务器上,第二次由 CollectionViewSource 过滤),但它具有以下优点:有一个集合 - 这使得集合通知的管理更加简单和容易。该集合是将提交到服务器的实际集合,这使得管理添加/删除更加简单,因为在提交回来时不会忘记从正确的集合中添加/删除实体以启动添加/删除功能。

我需要确认的最后一件事是:在 CollectionViewSource 上,我的理解是,在进行影响视图的多个更改时应该使用 DeferRefresh() 。这只是防止当内部更改可能导致刷新(例如配置排序、分组等)时发生不必要的刷新。当我们期望 UI 处理一些更新更改时,调用 .View.Refresh() 也很重要。.View.Refresh() 可能比 DeferRefresh() 更需要注意,因为它实际上会导致 UI 更新,而不是防止意外的 UI 更新。

我不知道这是否对其他人有帮助,但我希望如此。我确实花了一些时间来解决这些问题并试图理解这一点。如果您有任何澄清或其他需要补充的内容,请随时添加。

其他提示

瑞安,也许值得你花时间看一下 这篇关于集合绑定的文章 (以及一些相关的)。您的实现当然是一个合理的实现,但我可以看到它正在努力解决一些已经在框架级别解决的问题。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top