CommunityToolkit / CommunityToolkit/dotnet

Proposal: Implement ViewModel proxy generator for POCO entity graph

Đang mở
#1,069 0 bình luận 2 reaction 0 người được giao Xem trên GitHub
feature request :mailbox_with_mail:
Ngôn ngữ chính
C#
Star
3.8k
Fork
400
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

### 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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

No implementation files or tests are named. Start by reviewing the existing ObservableProperty generation and related issue #1058, then clarify the scope, generated API, change-notification behavior, cycle prevention, and what constitutes a complete ViewModel proxy generator.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
csharp
Lĩnh vực
desktop, frontend
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Cần làm rõ
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.