Laden...

Logik zum anlegen/bearbeiten von Datensätzen in DB-Anwendung

Erstellt von sugar76 vor 5 Jahren Letzter Beitrag vor 5 Jahren 2.934 Views
S
sugar76 Themenstarter:in
69 Beiträge seit 2017
vor 5 Jahren
Logik zum anlegen/bearbeiten von Datensätzen in DB-Anwendung

Hallo,

ich möchte gerne mal zur Diskussion stellen, wie die Logik zum Anlegen/Bearbeiten von Datensätzen in einer klassischen Datenbank-Anwendung idealerweise implementiert wird.

Technologien: WPF und Entity Framework.

Hierzu ein Mini-Beispiel, um Email-Adressen in der DB anzulegen/bearbeiten. Jede E-Mail-Adresse besitzt zwei Felder:

  • Die eigentliche Adresse
  • Einen optionalen Typ, z.B. Privat, Arbeit, ...

Ich habe es so umgesetzt, dass mit Detached-Objekten arbeite, die

  • an ein ViewModel zur Bearbeitung übergeben werden
  • vom Benutzer editiert werden
  • dann zum Speichern an einen Service.

Wie würdet Ihr das machen?

Gruß

View:


<UserControl x:Class="MyApp.View.EmailEditView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             Height="Auto" Width="400" x:Name="View">
    <DockPanel>         
        <Grid DockPanel.Dock="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <!-- Email-Adresse -->
            <Label Grid.Row="0" Grid.Column="0" Content="Email-Adresse"/>
            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Adresse, UpdateSourceTrigger=LostFocus, ValidatesOnNotifyDataErrors=True}" />
            
            <!-- Email-Typ -->
            <Label Grid.Row="1" Grid.Column="0" Content="Typ"/>
            <ComboBox
                Grid.Row="1" Grid.Column="1" 
                ItemsSource="{Binding EmailTypList}" 
                SelectedItem="{Binding EmailTyp, Mode=TwoWay}" 
                DisplayMemberPath="Name" />
        </Grid>

        <!-- Speichern/Abbrechen -->
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Content="Speichern" Command="{Binding Save}" CommandParameter="{Binding ElementName=View}" />
            <Button x:Name="BtnCancel" Content="Abbrechen" Click="BtnCancel_Click" />
        </StackPanel>
    </DockPanel>
</UserControl>

ViewModel:


namespace MyApp.ViewModel
{
    public class EmailEditViewModel : ValidatingViewModelBase
    {
        private MyApp.Entities.Email email;
	
        private string adresse;
        public string Adresse
        {
            get { return adresse; }
            set { adresse = value; OnPropertyChanged(); }
        }

        private MyApp.Entities.EmailTyp emailTyp;
        public MyApp.Entities.EmailTyp EmailTyp
        {
            get { return emailTyp; }
            set { emailTyp = value; OnPropertyChanged(); }
        }

        private List<MyApp.Entities.EmailTyp> emailTypList;
        public List<MyApp.Entities.EmailTyp> EmailTypList
        {
            get { return emailTypList; }
            set { emailTypList = value; OnPropertyChanged(); }
        }
        
        public ICommand Save { get; set; }

        public EmailEditViewModel(MyApp.Entities.Email email)
        {
            this.email = email;
			
            Adresse = email.Adresse;
            EmailTyp = email.EmailTyp;

            // Liste der Typen, z.B. Arbeit, Privat, Sonstige, ...
            EmailTypList = EmailTypService.GetAll();

            Save = new RelayCommand(SaveExecute, IsValid);
            
            RegisterValidator(() => Adresse, ValidateAdresse);
            ValidateAll();
        }

        protected async Task<string> ValidateAdresse()
        {
            return await EmailValidationRule.ValidateEmail(adresse);
        }

        protected override void UpdateDataModel()
        {
            email.EmailTyp = emailTyp;
            email.Adresse = adresse;
        }

        protected override void SaveExecute(object obj)
        {
            UpdateDataModel();
            EmailService.Save(email);
            Window.GetWindow(obj as DependencyObject).Close();
        }
    }
}

Service + Entitäten:


namespace MyApp.Service
{
    public class EmailService
    {
        public static void Save(MyApp.Entities.Email email)
        {
            using (var context = new MyAppEntities())
            {
                if (email.EmailTyp != null)
                {
                    if (email.EmailTyp.Id > 0)
                    {
                        // Nur den FK ohne referenziertes Objekt verwenden, um keine Duplikate beim Speichern zu erzeugen
                        email.EmailTypId = email.EmailTyp.Id;
                        email.EmailTyp = null;
                    }
                    else
                    {
                        context.Entry(email.EmailTyp).State = EntityState.Added;
                    }
                }

                context.Entry(email).State = email.Id == 0 ? EntityState.Added : EntityState.Modified;
                context.SaveChanges();
            }
        }
    }
}

namespace MyApp.Entities
{
    public partial class Email
    {
        public int Id { get; set; }
        public Nullable<int> EmailTypId { get; set; }
        
        public virtual EmailTyp EmailTyp { get; set; }
    }
    
    public partial class EmailTyp
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
16.806 Beiträge seit 2008
vor 5 Jahren

Statische Methoden sind weder modular noch testbar.
[Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

Ansonsten gilt auch hier
[Artikel] Drei-Schichten-Architektur

Die Entität sollte auch EMailAddress heissen; denn es ist eine E-Mail Adresse und keine E-Mail.
Auch der Name des EMailService macht wenig sinn, wenn es offensichtlich eher ein Repository ist; daher wäre wohl EMailAddressMssqlRepository mit einem IEMailAddressRepository Interface eher passend.

Warum ist der Typ bei Dir eine extra Klasse und nicht einfach ein ENum, wenn es eh nur zwei Typen gibt und offensichtlich keine Erweiterbarkeit notwendig ist?
Asynchrone Methoden sollten auch einen Async-Suffix haben.

Da es eine Review Frage ist, auch dahin verschoben.

S
sugar76 Themenstarter:in
69 Beiträge seit 2017
vor 5 Jahren

Statische Methoden sind weder modular noch testbar.

Ja, das steht noch auf meiner Liste ...

Ansonsten gilt auch hier

>

Die drei Schichten sind in dem Beispiel korrekt abgebildet, meine ich, oder? Bei MVVM übernimmt das ViewModel ja Aufgaben des Controllers (sprich: die Verbindung zwischen View und Datenmodell).

Ansonsten danke für die Hinweise zum Naming. Werde das berücksichtigen.

S
sugar76 Themenstarter:in
69 Beiträge seit 2017
vor 5 Jahren

Nachtrag: mich würde noch interessieren, wie die Daten aus dem ViewModel am besten an die Datenzugriffsschicht übergeben werden ...

Die ganzen Tutorials zum Thema MVVM/EF sind meist extrem einfach gehalten, kennt da jemand vielleicht ein Real-World-Beispiel?

EDIT: nachdem ich das geschrieben habe, hab ich noch mal gesucht und tatsächlich ein gutes Tutorial gefunden: https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/