Aplicación WPF CRUD usando DataGrid, MVVM Pattern, Entity Framework y C#.NET

 C Programming >> Programación C >  >> Tags >> WPF
Aplicación WPF CRUD usando DataGrid, MVVM Pattern, Entity Framework y C#.NET
¡Buen día a todos!

Este es un ejemplo de un proyecto WPF CRUD (crear, actualizar y eliminar) que usa el control DataGrid, ADO.NET Entity Framework 6.x, C#.NET y el patrón arquitectónico Model-View-Viewmodel(MVVM). Esta publicación se basó en este tutorial WPF CRUD con DataGrid, Entity Framework y C#.NET, excepto que ahora estamos usando el marco MVVM. Los pasos a continuación son bastante sencillos y fáciles de seguir.

Yo. Configuración del proyecto

1. Agregue una tabla llamada Estudiantes en su base de datos. El script completo se encuentra en esta publicación WPF CRUD con DataGrid, Entity Framework y C#.NET
2. Cree un proyecto WPF y agregue cuatro carpetas llamadas DataAccess, Model, View y ViewModel.
3. La estructura de su proyecto puede verse similar a la captura de pantalla proporcionada a continuación.

II. Codificación de la clase de modelo y repositorio

1. Dentro de la carpeta Modelo, agregue un Modelo de datos de entidad ADO.NET que se conecte a la tabla Estudiantes en su base de datos. Por mi parte, lo nombré StudentModel .
2. Para el nombre de la cadena de conexión, lo cambié a StudentEntities .
3. Lo siguiente es agregar un StudentRecord clase que tiene propiedades correspondientes a las columnas de la tabla y una propiedad ObservableCollection utilizada como fuente de elementos para DataGrid. Esta clase hereda el ViewModelBase clase agregada en la carpeta ViewModel que se mencionará en la sección Codificación de las clases de ViewModel para que haya un mecanismo en el manejo de cambios de propiedad y notificaciones de los controles a través del enlace de datos.
public class StudentRecord : ViewModelBase
{
	private int _id;
	public int Id
	{
		get
		{
			return _id;
		}
		set
		{
			_id = value;
			OnPropertyChanged("Id");
		}
	}

	private string _name;
	public string Name
	{
		get
		{
			return _name;
		}
		set
		{
			_name = value;
			OnPropertyChanged("Name");
		}
	}

	private int _age;
	public int Age
	{
		get
		{
			return _age;
		}
		set
		{
			_age = value;
			OnPropertyChanged("Age");
		}
	}

	private string _address;
	public string Address
	{
		get
		{
			return _address;
		}
		set
		{
			_address = value;
			OnPropertyChanged("Address");
		}
	}

	private string _contact;
	public string Contact
	{
		get
		{
			return _contact;
		}
		set
		{
			_contact = value;
			OnPropertyChanged("Contact");
		}
	}

	private ObservableCollection<StudentRecord> _studentRecords;
	public ObservableCollection<StudentRecord> StudentRecords
	{
		get
		{
			return _studentRecords;
		}
		set
		{
			_studentRecords = value;
			OnPropertyChanged("StudentRecords");
		}
	}

	private void StudentModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
	{
		OnPropertyChanged("StudentRecords");
	}
}
4. Agregue una clase de repositorio dentro de la carpeta DataAccess que realiza las operaciones CRUD hacia la base de datos.
public class StudentRepository
{
	private StudentEntities studentContext = null;

	public StudentRepository()
	{
		studentContext = new StudentEntities();
	}

	public Student Get(int id)
	{
		return studentContext.Students.Find(id);
	}

	public List<Student> GetAll()
	{
		return studentContext.Students.ToList();
	}

	public void AddStudent(Student student)
	{
		if (student != null)
		{
			studentContext.Students.Add(student);
			studentContext.SaveChanges();
		}
	}

	public void UpdateStudent(Student student)
	{
		var studentFind = this.Get(student.ID);
		if (studentFind != null)
		{
			studentFind.Name = student.Name;
			studentFind.Contact = student.Contact;
			studentFind.Age = student.Age;
			studentFind.Address = student.Address;
			studentContext.SaveChanges();
		}
	}

	public void RemoveStudent(int id)
	{
		var studObj = studentContext.Students.Find(id);
		if (studObj != null)
		{
			studentContext.Students.Remove(studObj);
			studentContext.SaveChanges();
		}
	}
}

III. Codificación de las clases ViewModel

1. Agregue una clase ViewModelBase que implemente la interfaz INofifyPropertyChanged. Esta interfaz básicamente informa a los clientes vinculantes que se ha actualizado un valor de propiedad. Esta clase es heredada por el modelo StudentRecord cuyas propiedades se usan en el enlace de datos y necesitaba algún tipo de notificación cuando se cambiaba el valor de una propiedad.
public class ViewModelBase : INotifyPropertyChanged
{
	public event PropertyChangedEventHandler PropertyChanged;

	protected void OnPropertyChanged(string propertyName)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}
}
2. Lo siguiente es agregar una clase RelayCommand que implemente la interfaz ICommand. Los comandos se usan para manejar eventos en WPF con respecto al patrón arquitectónico de MVVM. El único propósito de un comando es transmitir o distribuir su funcionalidad a otros objetos mediante la invocación de delegados. El valor de retorno predeterminado para un método CanExecute es verdadero. Una buena explicación de lo que es una clase de RelayCommand en stackoverflow.com.
public class RelayCommand : ICommand
{
	private readonly Action<object> _execute;
	private readonly Predicate<object> _canExecute;

	public RelayCommand(Action<object> execute)
		: this(execute, null)
	{
	}

	public RelayCommand(Action<object> execute, Predicate<object> canExecute)
	{
		if (execute == null)
			throw new ArgumentNullException("execute");
		_execute = execute;
		_canExecute = canExecute;
	}

	public bool CanExecute(object parameter)
	{
		return _canExecute == null ? true : _canExecute(parameter);
	}

	public event EventHandler CanExecuteChanged
	{
		add { CommandManager.RequerySuggested += value; }
		remove { CommandManager.RequerySuggested -= value; }
	}

	public void Execute(object parameter)
	{
		_execute(parameter);
	}
}
3. Lo último es crear una clase ViewModel que realice el enlace de comando de los botones que luego llama a métodos específicos que manejan las operaciones CRUD y la actualización de los valores de propiedad. Un ejemplo es SaveCommand enlazado con el botón Guardar en la vista. Cuando se hace clic en el botón Guardar, el comando Guardar ejecuta el método SaveData() y guarda la información en la base de datos y luego vuelve a cargar todo en el objeto Observable, que es el ItemSource de DataGrid. Aquí se presenta una explicación detallada y extensa de lo que hace MVVM Aplicaciones WPF con el patrón de diseño Model-View-ViewModel. La clase ViewModel aún se puede refactorizar como poner comandos y entidades en cada clase diferente más o menos. Pero para esta demostración, terminé poniendo todo aquí.
public class StudentViewModel
{
	private ICommand _saveCommand;
	private ICommand _resetCommand;
	private ICommand _editCommand;
	private ICommand _deleteCommand;
	private StudentRepository _repository;
	private Student _studentEntity = null;
	public StudentRecord StudentRecord { get; set; }
	public StudentEntities StudentEntities { get; set; }

	public ICommand ResetCommand
	{
		get
		{
			if (_resetCommand == null)
				_resetCommand = new RelayCommand(param => ResetData(), null);

			return _resetCommand;
		}
	}

	public ICommand SaveCommand
	{
		get
		{
			if (_saveCommand == null)
				_saveCommand = new RelayCommand(param => SaveData(), null);

			return _saveCommand;
		}
	}

	public ICommand EditCommand
	{
		get
		{
			if (_editCommand == null)
				_editCommand = new RelayCommand(param => EditData((int)param), null);

			return _editCommand;
		}
	}

	public ICommand DeleteCommand
	{
		get
		{
			if (_deleteCommand == null)
				_deleteCommand = new RelayCommand(param => DeleteStudent((int)param), null);

			return _deleteCommand;
		}
	}

	public StudentViewModel()
	{
		_studentEntity = new Student();
		_repository = new StudentRepository();
		StudentRecord = new StudentRecord();
		GetAll();
	}

	public void ResetData()
	{
		StudentRecord.Name = string.Empty;
		StudentRecord.Id = 0;
		StudentRecord.Address = string.Empty;
		StudentRecord.Contact = string.Empty;
		StudentRecord.Age = 0;
	}

	public void DeleteStudent(int id)
	{
		if (MessageBox.Show("Confirm delete of this record?", "Student", MessageBoxButton.YesNo)
			== MessageBoxResult.Yes)
		{
			try
			{
				_repository.RemoveStudent(id);
				MessageBox.Show("Record successfully deleted.");
			}
			catch (Exception ex)
			{
				MessageBox.Show("Error occured while saving. " + ex.InnerException);
			}
			finally
			{
				GetAll();
			}
		}
	}

	public void SaveData()
	{
		if (StudentRecord != null)
		{
			_studentEntity.Name = StudentRecord.Name;
			_studentEntity.Age = StudentRecord.Age;
			_studentEntity.Address = StudentRecord.Address;
			_studentEntity.Contact = StudentRecord.Contact;

			try
			{
				if (StudentRecord.Id <= 0)
				{
					_repository.AddStudent(_studentEntity);
					MessageBox.Show("New record successfully saved.");
				}
				else
				{
					_studentEntity.ID = StudentRecord.Id;
					_repository.UpdateStudent(_studentEntity);
					MessageBox.Show("Record successfully updated.");
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show("Error occured while saving. " + ex.InnerException);
			}
			finally
			{
				GetAll();
				ResetData();
			}
		}
	}

	public void EditData(int id)
	{
		var model = _repository.Get(id);
		StudentRecord.Id = model.ID;
		StudentRecord.Name = model.Name;
		StudentRecord.Age = (int)model.Age;
		StudentRecord.Address = model.Address;
		StudentRecord.Contact = model.Contact;
	}

	public void GetAll()
	{
		StudentRecord.StudentRecords = new ObservableCollection<StudentRecord>();
		_repository.GetAll().ForEach(data => StudentRecord.StudentRecords.Add(new StudentRecord()
		{
			Id = data.ID,
			Name = data.Name,
			Address = data.Address,
			Age = Convert.ToInt32(data.Age),
			Contact = data.Contact
		}));
	}
}

IV. Enlace de datos y vista

1. Por último, pero no menos importante, está la vista. Mueva la página MainWindow a la carpeta Ver del proyecto. En el método constructor, establezca la clase DataContext con la clase StudentViewModel. Puede optar por configurar DataContext a través de XAML.
public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new StudentViewModel();
        }
    }
2. Lo siguiente es agregar varios controles como cuadros de texto para aceptar entradas, botones para activar eventos y el control DataGrid para mostrar toda la información actualizada de la base de datos. Estos controles se han pegado a la clase ViewModel a través de Binding propiedad. Los controles de entrada están agrupados en un panel GroupBox, mientras que los botones Guardar y Restablecer están dentro del contenedor StackPanel. DataGrid también está dentro del contenedor StackPanel y cada uno de estos contenedores está dispuesto horizontalmente dentro de un contenedor principal StackPanel.
<Window x:Class="MVVMDemo.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MVVMDemo.View"        
        mc:Ignorable="d"
        Title="Basic Create Update Delete With MVVM"
        Height="500" Width="600">
    <StackPanel Orientation="Vertical">
        <GroupBox Header="Student Form" Margin="10">
            <Grid Height="150">
                <Grid.RowDefinitions>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="Name" HorizontalAlignment="Left" 
                       VerticalContentAlignment="Center" Grid.Column="0" Grid.Row="0"/>
                <TextBox Grid.Row="0" Grid.Column="1" x:Name="TextBoxName" Height="27" 
                       Text="{Binding Path=StudentRecord.Name, Mode=TwoWay}"  Margin="5"  Width="300" HorizontalAlignment="Left"/>
                <Label Content="Age" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="1" Grid.Column="0"/>
                <TextBox Grid.Row="1" Grid.Column="1" x:Name="TextBoxAge" Height="27" 
                       Text="{Binding Path=StudentRecord.Age, Mode=TwoWay}" Margin="5" Width="70" HorizontalAlignment="Left"/>
                <TextBlock Grid.Row="1" Grid.Column="1" x:Name="TextBlockId" 
                       Visibility="Hidden" Text="{Binding Path=StudentRecord.Id, Mode=TwoWay}"/>
                <Label Content="Address" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="2" Grid.Column="0" />
                <TextBox Grid.Row="2" Grid.Column="1" x:Name="TextBoxAddress" Height="27" 
                       Text="{Binding Path=StudentRecord.Address, Mode=TwoWay}" Margin="5" Width="300" HorizontalAlignment="Left"/>
                <Label Content="Contact" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="3" Grid.Column="0" />
                <TextBox Grid.Row="3" Grid.Column="1" x:Name="TextBoxContact" Height="27"
                       Text="{Binding Path=StudentRecord.Contact, Mode=TwoWay}" Margin="5" Width="300" HorizontalAlignment="Left"/>
            </Grid>
        </GroupBox>
        <StackPanel Height="40" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="ButtonSave" Content="Save" Height="30" Width="80"
                    Command="{Binding SaveCommand}"/>
            <Button x:Name="ButtonCancel" Content="Cancel" Height="30" Width="80" 
                    Command="{Binding ResetCommand}" Margin="5,0,10,0"/>
        </StackPanel>
        <StackPanel Height="210">
            <DataGrid x:Name="DataGridStudents" AutoGenerateColumns="False"
                      ItemsSource="{Binding StudentRecord.StudentRecords}" CanUserAddRows="False" Height="200" Margin="10">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Path=Id}" Visibility="Hidden"/>
                    <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="100"  IsReadOnly="True"/>
                    <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}" Width="50"  IsReadOnly="True"/>
                    <DataGridTextColumn Header="Address" Binding="{Binding Path=Address}" Width="180" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Contact" Binding="{Binding Path=Contact}" Width="125" IsReadOnly="True"/>
                    <DataGridTemplateColumn Width="50">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Select" x:Name="ButtonEdit" CommandParameter="{Binding Path=Id}"
                                        Command="{Binding Path=DataContext.EditCommand,RelativeSource={RelativeSource FindAncestor,
                                                AncestorType=Window}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Width="50">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Delete" x:Name="ButtonDelete" CommandParameter="{Binding Path=Id}"
                                        Command="{Binding Path=DataContext.DeleteCommand, RelativeSource={RelativeSource FindAncestor,
                                                AncestorType=Window}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
    </StackPanel>
</Window>
Salida
No