CommunityToolkit / CommunityToolkit/dotnet

Proposal: Implement ViewModel proxy generator for POCO entity graph

未关闭
#1,069 0 条评论 2 个 reaction 已指派 0 人 在 GitHub 查看
feature request :mailbox_with_mail:
主要语言
C#
星标
3.8k
派生
400
PR 合并指标
30 天内没有已合并 PR

描述

### Overview

I wonder if this has been considered before, we often have complex POCO entity graphs which we want to reuse in our XAML app.

It would be so much easier to have ViewModel wrappers generated for the entire entity graph, that includes:

- Inheriting a common `EntityViewModel` base class
- Wrapper properties for scalar values (similar to what's currently generated in `ObservableProperty`)
- Wrapper `ObservableCollection` properties for `ICollection` etc. entity properties (notify parent for collection changes?)
- Wrapper `EntityViewModel` properties for other entity navigations (notify parent?)
- Wrapper `ObservableCollection` for entity-collection navigations (notify parent?)
- Implement `IChangeTracking` / `IRevertibleChangeTracking`?

### API breakdown

*All code is pseudo prototyping on the fly to give a general idea.

```csharp
public abstract class ViewModelProxyBase(Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
: ObservableValidator, INotifyParentPropertyChange;
{
private Action? ParentPropertyChangedAction;
private Action? ParentPropertyChangingAction;
}

public abstract class ViewModelProxyBase(TModel model, Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
: ViewModelProxyBase(parentPropertyChangedAction, parentPropertyChangingAction)
where TModel : class
{
public TModel Model => model;
}

public abstract class ObservableProxyCollectionSimple : ObservableCollection
{
public ObservableProxyCollectionSimple(Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
{
OnCollectionChanged += (sender, e) => parentPropertyChangedAction?.Invoke();
}
}

public abstract class ObservableProxyCollectionComplex : ObservableCollection>
{
private Action? _parentPropertyChangedAction;
private Action? _parentPropertyChangingAction;

public ObservableProxyCollectionSimple(Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
{
_parentPropertyChangedAction = parentPropertyChangedAction;
_parentPropertyChangingAction = parentPropertyChangingAction;
OnCollectionChanged += OnCollectionChanged;
}

private void OnCollectionChanged(...)
{
// removed items - stop tracking
// new items - start tracking
}
}
```

### Usage example

```csharp
public class Entity
{
public int Id { get; set; }
public Reference? Reference { get; set; }
public ICollection { get; set; } = [];
}

public class Reference
{
public string Name { get; set; }
public bool IsEnabled { get; }
}

public partial class EntityViewModel(Entity entity, Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
: ViewModelProxyBase(entity, parentPropertyChangedAction, parentPropertyChangingAction)
{
}

public partial class ReferenceViewModel(Reference reference, Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
: ViewModelProxyBase(reference, parentPropertyChangedAction, parentPropertyChangingAction)
{
}
```

### What's generated:

```csharp
public partial class EntityViewModel(Entity entity, Action? parentPropertyChangedAction, Action? parentPropertyChangingAction = default)
: ViewModelProxyBase(entity, parentPropertyChangedAction, parentPropertyChangingAction)
{
public int Id
{
get => Model.Id;
set
{
if(Equals(Model.Id, value))
return;

OnPropertyChanging();
parentPropertyChangingAction?.Inovke();
Model.Id = value;
OnPropertyChanged();
parentPropertyChangedAction?.Inovke();
}
}

private ReferenceViewModel? _Reference;
public ReferenceViewModel? Reference
{
get
{
if(_Reference == null && Model.Reference != null)
{
_Reference = new ReferenceViewModel(Model.Reference, this);
}

return _Reference;
}
set
{
if(Equals(Model.Reference, value))
return;

OnPropertyChanging();
parentPropertyChangingAction?.Inovke();

if(_Reference != null)
{
_Reference.Parent = null;
}

value.Parent = this;
Model.Reference = value.Model;
OnPropertyChanged();
parentPropertyChangedAction?.Inovke();
}
}

public ObservableCollectionProxyComplex References
{
...
}
}

public partial class ReferenceViewModel(Reference reference, INotifyParent? parent = default)
: ViewModelProxyBase(reference, parent)
{
public string Name
{
get => Model.Name;
set
{
if(Equals(Model.Name, value))
return;

OnPropertyChanging();
Model.Id = value;
OnPropertyChanged();
}
}

public bool IsEnabled => Model.IsEnabled;
}
```

### Breaking change?

No

### Alternatives

Unknown

### Additional context

- Thought has to be given to prevent cyclical change notification.
- Related #1058

### Help us help you

Yes, but only if others can assist

贡献指南

打开贡献指南

调研方向

未指定实现文件或测试。首先查看现有的 ObservableProperty 生成机制和相关 issue #1058,然后明确范围、生成的 API、变更通知行为、循环防止机制,以及什么才算是一个完整的 ViewModel 代理生成器。

由索引模型根据 Issue 内容生成。

评估

技术栈
csharp
领域
desktop, frontend
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
停滞
描述清晰度
需要澄清
新手友好度
25/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。