使用 MVVM 和 RIA 服务的不同绑定方法的优缺点

发布于 2024-09-12 21:07:41 字数 491 浏览 2 评论 0原文

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

我真正看到的选择是:

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

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

I have been building an application, which uses the LoadOperation's Entities to return an IEnumerable which becomes the source of a CollectionViewSource in my View Model. I am now discovering the potential pitfall to this approach, when adding Entities in my Silverlight client, I cannot see these entities, unless I either submit the New Entity, then reload, or Maintain a separate collection of items, which I am binding to.

What I really see as my options are:

  1. Add an ObservableCollection to use as the Source of the CollectionViewSource property in my ViewModel - this way I can add to both the DomainContext and the ObservableCollection at the same time to keep the collections in sync.
  2. Change the Binding to the EntitySet directly, and add a filtering event handler to provide the filtering on the CollectionViewSource.

If anyone has tips or thoughts about pros/cons of each, I would greatly appreciate it. In particular, I am wondering, if there are performance and/or programming benefits in favor of one or the other?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

稚然 2024-09-19 21:07:41

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

我的演示的基础是单个经过身份验证的域服务,它返回单个资源实体。我将公开 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 的配置中添加过滤事件处理程序,并且可以视为过滤数据 2 次(在服务器上 1 次,第二次由 CollectionViewSource 实现),它具有以下优点: 有一个集合 - 这使得集合通知的管理更简单、更容易。该集合是将提交到服务器的实际集合,这使得管理添加变得容易。 /deletes 更简单,因为在提交回来时不会忘记从正确的集合中添加/删除实体以启动添加/删除功能。

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

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

I am taking this one approach at a time. First, I am going to show a point of reference to dicuss this with, then I will highlight the different changes necessary to support each methodology.

The basis for my demo is a single, authenticated domain service which returns a single entity of Resource. I will expose 4 commands (save, undo, add, and delete), plus a Collection, and a Property to hold the SelectedResource.

2 Different classes implement this interface (1 for blending, 1 for production). The production is the only one I will discuss here. Notice the action(lo.Entities) in the GetMyResources function:

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);
    }
}

In the ViewModel, I have the following Implementation:

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);
        });
    }
}

The alternate method would involve changing the WorkProvider class as follows (notice the action(workContext.Resources) that is returned:

    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);
    }

And the changes to the viewmodel are as follows (notice the removal of the _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);
        });
    }
}

While the second approach definately requires adding the filter event handler in the configuration of the CollectionViewSource, and could be seen as filtering data 2 times (1 time at the server, and the second time by the CollectionViewSource), it does off the following benefits: There is a single collection - which makes management of collection notifications simpler and easier. The collection is the actual collection which will be submitted to the server, which makes managing adds/deletes simpler, since there are not opportunities for forgetting to add/remove entities from the correct collection to initiate the add/delete function when submitting back.

The one last thing I need to confirm is the following: On a collectionviewsource, it is my understanding that you should use DeferRefresh() when making multiple changes that affect the view. This just prevents unnecessary refreshes from occuring when internal changes may cause refreshes such as configuring sorting, grouping, etc. It is also important to call .View.Refresh() when we expect the UI to process some update changes. The .View.Refresh() is probably more important to note than the DeferRefresh(), since it actually causes a UI update, as opposed to a prevent unexpected UI updates.

I don't know if this will help others, but I hope so. I definately spent some time working through these and trying to understand this. If you have clarifications or other things to add, please feel free to do so.

萌化 2024-09-19 21:07:41

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

Ryan, it might be worth your while to take a look through this post on collection binding (and some of the related ones). Your implementation is certainly a reasonable one, but I can see it wrestles with a few of the issues that have already been resolved at the framework level.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文