Prós / contras para diferentes abordagens de ligação usando serviços MVVM e RIA
-
25-09-2019 - |
Pergunta
Eu tenho construído um aplicativo, que usa as entidades da LoadOperation para devolver um IEnumerable, que se torna a fonte de uma Coleção ViewSource no meu modelo de visualização. Agora estou descobrindo a possível armadilha para essa abordagem, ao adicionar entidades no meu cliente Silverlight, não consigo ver essas entidades, a menos que envie a nova entidade, depois recarregue ou mantenha uma coleção separada de itens, aos quais estou vinculado.
O que eu realmente vejo como minhas opções são:
- Adicione uma observação ObservableCollection a ser usada como fonte da propriedade Coleção ViewSource no meu ViewModel - dessa maneira posso adicionar ao domainContext e à ObservableCollection ao mesmo tempo para manter as coleções sincronizadas.
- Altere diretamente a ligação ao EntitySet e adicione um manipulador de eventos de filtragem para fornecer a filtragem no Coleção ViewSource.
Se alguém tiver dicas ou pensamentos sobre prós/contras de cada um, eu apreciaria muito. Em particular, estou me perguntando, se há benefícios de desempenho e/ou programação em favor de um ou outro?
Solução
Estou adotando essa abordagem de cada vez. Primeiro, vou mostrar um ponto de referência à DiCUSS com isso, então destacarei as diferentes mudanças necessárias para apoiar cada metodologia.
A base para minha demonstração é um único serviço de domínio autenticado, que retorna uma única entidade de recurso. Exporei 4 comandos (salvar, desfazer, adicionar e excluir), além de uma coleção e uma propriedade para manter o SelectedResource.
2 classes diferentes implementam essa interface (1 para mistura, 1 para produção). A produção é a única que discutirei aqui. Observe a ação (lo.entities) na função getMyResources:
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);
}
}
No ViewModel, tenho a seguinte implementação:
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);
});
}
}
O método alternativo envolveria a alteração da classe WorkProvider da seguinte forma (observe a ação (WorkContext.resources) que é devolvida:
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);
}
E as mudanças no ViewModel são as seguintes (observe a remoção da _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);
});
}
}
Embora a segunda abordagem definitivamente exija a adição do manipulador de eventos do filtro na configuração do ColeçãoViewSource, e possa ser visto como dados de filtragem 2 vezes (1 tempo no servidor e a segunda vez pelo ColeçãoViewSource), ela refere os seguintes benefícios: Há uma única coleção - que torna o gerenciamento das notificações de coleta mais simples e fácil. A coleção é a coleção real que será enviada ao servidor, o que simplifica o gerenciamento/exclusão mais simples, pois não há oportunidades para esquecer de adicionar/remover entidades da coleção correta para iniciar a função Adicionar/excluir ao enviar novamente.
A última coisa que preciso confirmar é a seguinte: em uma Coleção ViewSource, é meu entendimento de que você deve usar o adjingRefresh () ao fazer várias alterações que afetam a visualização. Isso apenas impede que as atualizações desnecessárias ocorram quando alterações internas podem causar atualizações como configurar a classificação, o agrupamento etc. Também é importante chamar .View.Refresh () quando esperamos que a interface do usuário processe algumas alterações de atualização. O .View.Refresh () é provavelmente mais importante para observar do que o adjustefresh (), pois realmente causa uma atualização da interface do usuário, em oposição a uma atualização inesperada da interface do usuário.
Não sei se isso ajudará os outros, mas espero que sim. Definitivamente, passei algum tempo trabalhando com isso e tentando entender isso. Se você tem esclarecimentos ou outras coisas a acrescentar, sinta -se à vontade para fazê -lo.
Outras dicas
Ryan, pode valer a pena dar uma olhada Esta postagem na coleta de coleção (e alguns dos relacionados). Sua implementação é certamente razoável, mas posso vê -la lutar com alguns dos problemas que já foram resolvidos no nível da estrutura.