Привязка данных и LINQ

98

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

В WPF поддерживается язык интегрированных запросов (Language Integrated Query — LINQ), предлагающий синтаксис запросов общего назначения, который работает с широким разнообразием источников данных и тесно интегрирован в язык C#.

Язык LINQ работает с любым источником, для которого имеется соответствующий поставщик LINQ. Поддержка, включенная в .NET, дает возможность использовать одинаково структурированные запросы LINQ для извлечения данных из коллекции, находящейся в памяти, из файла XML либо из базы данных SQL Server. Как и другие языки запросов, LINQ позволяет фильтровать, сортировать и трансформировать извлекаемые данные.

Давайте предположим, что нужно добавить в созданное ранее приложение функциональность сортировки коллекции машин по стоимости. Ниже я приведу развернутый пример, который включает в себя все то, что мы изучили в предыдущих статьях с добавлением новой функциональности посредством LINQ (в проект потребуется добавить окно AddWindow.xaml):

// Вспомогательные методы в сущностном классе CarTable (файл AutoShopDataModel.Designer.cs)
public partial class CarTable : EntityObject
{
        // Свойство, содержащее коллекцию объектов CarTable хранящихся в базе данных
        public static ObservableCollection<CarTable> Cars
        {
            get
            {
                AutoShopEntities context = new AutoShopEntities();

                return new ObservableCollection<CarTable>(
                        context.CarTables.Select(s => s).ToList());
            }
        }

        public override string ToString()
        {
            return ModelName + " " + ModelNumber;
        }
        
        ...
<!-- MainWindow.xaml -->

<Window x:Class="DataBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:databinding="clr-namespace:DataBinding"
        Title="База данных AutoShop" Height="650" Width="440">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>

            <ListBox Name="lstCars" Margin="5" />

            <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal" Margin="5,2,5,10">
                <Button Margin="2,0,0,0"  Padding="2" Content="Удалить" Click="removeCar_Click"/>
                <Button Margin="2,0,0,0"  Padding="2" Content="Добавить" Click="addCar_Click"/>
                <Button Margin="2,0,0,0"  Padding="2" Content="Ошибки" Click="getErrors_Click"/>
            </StackPanel>
            <StackPanel Grid.Row="2" HorizontalAlignment="Right" Orientation="Horizontal" Margin="5,2,5,10">
                <TextBlock>Max стоимость: </TextBlock>
                <TextBox x:Name="txbMaxCost" Width="80" HorizontalContentAlignment="Center" Margin="8,0,8,0"/>
                <Button Content="Получить" Click="getCarsMaxCost_Click"/>
            </StackPanel>
        </Grid>

        <GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" ResizeBehavior="PreviousAndNext" Height="5"/>

        <Border Grid.Row="2" Padding="7" Margin="7" Background="LightSteelBlue">
            <Border.Resources>
                <Style TargetType="{x:Type TextBox}">
                    <Setter Property="Validation.ErrorTemplate">
                        <Setter.Value>
                            <ControlTemplate>
                                <DockPanel LastChildFill="True">
                                    <TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="16" FontWeight="Bold" Text="X"/>
                                    <Border BorderBrush="Green" BorderThickness="1">
                                        <AdornedElementPlaceholder Name="adorner"/>
                                    </Border>
                                </DockPanel>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <Trigger Property="Validation.HasError" Value="True">
                            <Setter Property="Foreground" Value="Red"/>
                            <Setter Property="ToolTip" 
                                    Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Border.Resources>
            <Grid  Name="gridCarDetails" DataContext="{Binding ElementName=lstCars, Path=SelectedItem}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="*"></RowDefinition>
                </Grid.RowDefinitions>

                <TextBlock Margin="7">Марка:</TextBlock>
                <TextBox Margin="5" Grid.Column="1" LostFocus="textChange_Event">
                    <TextBox.Text>
                        <Binding Path="ModelName" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <databinding:EmptyRule></databinding:EmptyRule>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBlock Margin="7" Grid.Row="1">Модель:</TextBlock>
                <TextBox Margin="5" Grid.Row="1" Grid.Column="1" LostFocus="textChange_Event">
                    <TextBox.Text>
                        <Binding Path="ModelNumber" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <databinding:EmptyRule></databinding:EmptyRule>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBlock Margin="7" Grid.Row="2">Цена (руб):</TextBlock>
                <TextBox Margin="5" Grid.Row="2" Grid.Column="1" 
                         Text="{Binding Cost, ValidatesOnExceptions=True}"
                         LostFocus="textChange_Event"></TextBox>
                <TextBlock Margin="7,7,7,0" Grid.Row="3">Описание:</TextBlock>
                <TextBox Margin="7" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
                         VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" 
                         Text="{Binding Path=Description, TargetNullValue=Описание не доступно}" LostFocus="textChange_Event"/>
            </Grid>
        </Border>
    </Grid>
</Window>
// MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Collections.ObjectModel;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            lstCars.ItemsSource = CarTable.Cars;
        }

        // Обновляем список, соответствующий максимальной введенной стоимости
        private void getCarsMaxCost_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Double d = Double.Parse(txbMaxCost.Text);
                AutoShopEntities context = new AutoShopEntities();

                // Используем LINQ
                lstCars.ItemsSource = new ObservableCollection<CarTable>(
                    context.CarTables.Where(s => s.Cost <= d).Select(p => p));
            }
            catch
            {
            }
        }

        // Удалить запись из списка используя LINQ (без удаления из базы данных)
        private void removeCar_Click(object sender, RoutedEventArgs e)
        {
            ((ObservableCollection<CarTable>)lstCars.ItemsSource).Remove((CarTable)lstCars.SelectedItem);
        }

        // Добавить запись в коллекцию
        private void addCar_Click(object sender, RoutedEventArgs e)
        {
            AddWindow awin = new AddWindow();
            awin.ShowDialog();
            if (awin.AddWinCarTable != null)
            {
                ((ObservableCollection<CarTable>)lstCars.ItemsSource).Add(awin.AddWinCarTable);
            }
        }

        // Запись данных в базу при редактировании полей (LINQ to Entities)
        private void textChange_Event(object sender, RoutedEventArgs e)
        {
            CarTable ct = (CarTable)gridCarDetails.DataContext;

            // Используем LINQ to Entities для обновления базы данных
            AutoShopEntities context = new AutoShopEntities();

            CarTable ct1 = context.CarTables
                                  .Where(p => p.ID == ct.ID)
                                  .Single<CarTable>();

            ct1.ModelName = ct.ModelName;
            ct1.ModelNumber = ct.ModelNumber;
            ct1.Cost = ct.Cost;
            ct1.Description = ct.Description;

            context.SaveChanges();
        }

        // Получение списка ошибок
        private void getErrors_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            GetErrors(sb, gridCarDetails);
            string message = sb.ToString();
            if (message != "") MessageBox.Show(message);
        }

        private void GetErrors(StringBuilder sb, DependencyObject obj)
        {
            foreach (object child in LogicalTreeHelper.GetChildren(obj))
            {
                TextBox element = child as TextBox;
                if (element == null) continue;

                if (Validation.GetHasError(element))
                {
                    sb.Append(element.Text + " найдена ошибка:\r\n");
                    foreach (ValidationError error in Validation.GetErrors(element))
                    {
                        sb.Append("  " + error.ErrorContent.ToString());
                        sb.Append("\r\n");
                    }
                }

                GetErrors(sb, element);
            }
        }

    }

    // Специальное правило проверки достоверности полей ModelName и ModelNumber
    public class EmptyRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            if (((string)value).Length == 0)
                return new ValidationResult(false, "Пустое значение поля");
            
            return new ValidationResult(true, null);
        }
    }
}
<!-- AddWindow.xaml -->
   
<Window x:Class="DataBinding.AddWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="AddWindow" Height="420" Width="360" WindowStyle="None">
    <Grid Background="LightSteelBlue">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.Resources>
            <Style TargetType="{x:Type TextBox}">
                <Setter Property="VerticalContentAlignment" Value="Center"/>
                <Setter Property="Margin" Value="10,5,20,5"/>
            </Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Margin" Value="10,5,5,5"/>
            </Style>
        </Grid.Resources>
        <TextBlock Text="Id"/><TextBox x:Name="id" Grid.Column="1"/>
        <TextBlock Text="ModelName" Grid.Row="1"/><TextBox x:Name="ModelName" Grid.Column="1" Grid.Row="1"/>
        <TextBlock Text="ModelNumber" Grid.Row="2"/><TextBox x:Name="ModelNumber" Grid.Column="1" Grid.Row="2"/>
        <TextBlock Text="CategoryId" Grid.Row="3"/><TextBox x:Name="CategoryId" Grid.Column="1" Grid.Row="3"/>
        <TextBlock Text="CategoryName" Grid.Row="4"/><TextBox x:Name="CategoryName" Grid.Column="1" Grid.Row="4"/>
        <TextBlock Text="Cost" Grid.Row="5"/><TextBox x:Name="Cost" Grid.Column="1" Grid.Row="5" />
        <TextBlock Text="ImageCar" Grid.Row="6"/><TextBox x:Name="ImageCar" Grid.Column="1" Grid.Row="6"/>
        <TextBlock Text="Description" Grid.Row="7"/><TextBox x:Name="Description" Grid.Column="1" Grid.Row="7" 
                                                             VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" VerticalContentAlignment="Top"/>
        <StackPanel Grid.Row="8" Orientation="Horizontal" HorizontalAlignment="Center" Grid.ColumnSpan="2">
            <Button Margin="8,6" Padding="3" Content="Добавить" Click="Add_Click"/>
            <Button Margin="0,6" Padding="3" Content="Отмена" Click="Cancel_Click"/>
        </StackPanel>
    </Grid>
</Window>
// AddWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for AddWindow.xaml
    /// </summary>
    public partial class AddWindow : Window
    {
        public CarTable AddWinCarTable { get; set; }

        public AddWindow()
        {
            InitializeComponent();
        }

        private void Add_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                AddWinCarTable = new CarTable
                {
                    ID = Int32.Parse(id.Text),
                    CategoryID = Byte.Parse(CategoryId.Text),
                    CategoryName = CategoryName.Text,
                    ModelName = ModelName.Text,
                    ModelNumber = ModelNumber.Text,
                    Cost = Double.Parse(Cost.Text),
                    ImageCar = ImageCar.Text,
                    Description = Description.Text
                };
                this.Close();
            }
            catch
            {
                MessageBox.Show("Заданы некорректные данные");
            }
        }

        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            AddWinCarTable = null;
            this.Close();
        }
    }
}
Пройди тесты
Лучший чат для C# программистов