Страницы: 1
RSS
Использование dynamic в .NET EntityFramework
 
Хочу поделиться одной идеей, как в C# можно эффективно использовать dynamic в EF (на примере WPF приложения). Например, есть три таблицы в базе данных (по сути - справочники) с одной и той же структурой:
Код
class Strategy
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Operator
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class OrderState
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Задача: создать функционал для изменения значений справочников. У нас есть два комбобокса (ComboBox) и текстовое поле (TextBox):
1) сmbDictionaries - список доступных справочников
2) cmbDictionaryEntries - список значений в выбранном справочнике
3) txtEntryName - выбранное значение справочника

Итак, сначала подгружаем список доступных справочников:
Код
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
    // Создаём контекст
    context = new TestContext(@"Server=(localdb)\MSSQLLocalDB;Database=Test;Trusted_Connection=Yes;");

    // Загрузка справочников
    var dic = new Dictionary<string, string>
    {
        ["Стратегия"] = "Strategy",
   ["Оператор"] = "Operator",
   ["Статус"] = "OrderState"
    };

    foreach (var item in dic)
    {
        cmbDictionaries.Items.Add(new ComboBoxItem { Content = item.Key, Tag = item.Value });
    }
}

Заполняем комбобокс при перевыборе справочника:
Код
private async void OnDictionaryChanged(object sender, SelectionChangedEventArgs e)
{
    var set = GetDbSet();
    set.Load();
    cmbDictionaryEntries.ItemsSource = null;
    cmbDictionaryEntries.ItemsSource = await set.ToListAsync();
}

Создание и изменение:
Код
private async void OnChangeEntry(object sender, RoutedEventArgs e)
{
    // Taк как Binding у текстового поля использует режим TwoWay,
    // то все изменения транслируются автоматически в сущность.
    // По умолчанию, у текстового поля изменения транслируются при потере фокуса.
    int count = await context.SaveChangesAsync();
    MessageBox.Show($"Сохранено записей: {count}");
}

private async void OnAddEntry(object sender, RoutedEventArgs e)
{
    var set = GetDbSet();
    // Используем dynamic, чтобы проще было добраться до свойства Name
    dynamic entry = set.Create();
    entry.Name = txtEntryName.Text;
    set.Add(entry);
    int count = await context.SaveChangesAsync();
    MessageBox.Show($"Сохранено записей: {count}");
}

На данный момент мы уже с помощью dynamic облегчаем себе работу. Так как у нас таблицы (и сущности) одинаковые, то мы можем использовать обобщённый (generic) метод Set, чтобы создавать новые сущности и сохранять их. Вся изюминка - в методе GetDbSet:
Код
private DbSet GetDbSet()
{
    // cmbDictionaries в качестве ItemContainer использует ComboBoxItem.
    // Ранее был показан код добавления значений словаря. В свойство Tag было записано название класса сущности.
    // Здесь SelectedItem возвращает нам Object, а в конечном итоге - ComboBoxItem, у которого мы должны взять Tag.
    // По идее, можно сделать кастинг SelectedItem'a в ComboBoxItem, взять свойство Tag и сконвертировать в String...
    // Но используя dynamic, всё становится намного проще. В итоге setName имеет тип String. В этом можно убедиться,
    // если в Immediate Window выполнить запрос: ?setName.GetType().FullName
    // Ответом будет "System.String".
    dynamic setName = ((dynamic)cmbDictionaries.SelectedItem).Tag;
    // После получения названия целевой сущности, нужно получить её тип.
    // Нужно в строке указать полный путь к классу. Здесь для удобства используется фишка C#7.0 - Interpolation String.
    var type = Type.GetType($"DynamicEFWPF.{setName}");
    // Далее получаем DbSet, но не типизированный (Object).
    var set = context.Set(type);
    return set;
    // Или всё в одну строку:
    //return context.Set(Type.GetType($"DynamicEFWPF.{((dynamic)cmbDictionaries.SelectedItem).Tag}"));
}

Таким образом, нам не надо плодить кучу if'ов, чтобы выбрать тип. Но опять же повторюсь - это работает только для сущностей одинаковой структуры.
Полный WPF проект можно посмотреть/скачать здесь. Скрипт для создания базы данных - файл Scripts.sql в папке Database (работает с SQL Server 2016 и выше).
There is no knowledge that is not power
 
Цитата
Например, есть три таблицы в базе данных (по сути - справочники) с одной и той же структурой:
на мой взгляд dynamic упрощает код, но после это может аукнуться т.к. усложняет отладку и т.п.
я бы скорее интерфейсом объединил одинаковые сущьности
 
Цитата
pharmaprofi написал:
т.к. усложняет отладку
От dynamic'а ждать другого нельзя, но зато упрощает код) Кстати, удобно также использовать для сериализации/десериализации:
Код
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;


namespace DeserializeDynamic
{
    class Program
    {
        [Serializable]
        class Food
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

   static void Main(string[] args)
   {
            var listFood = new List<Food>
            {
                new Food { Id = 1, Name = "Watermelon"},
                new Food { Id = 2, Name = "Apple"}
            };
         
            // Имитация сериализации, передачи и десериализации
            using (var ms = new MemoryStream())
            {
               
                // Сериализация
                var binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(ms, listFood);

                // Передача данных
                ms.Seek(0, SeekOrigin.Begin);

                //Десериализация
                dynamic foodList = binaryFormatter.Deserialize(ms);
                foreach(var food in foodList)
                {
                    Console.WriteLine($"Id: {food.Id}, Name: {food.Name}");
                }

            }

        }
    }
}
Изменено: SuperCat - 18.09.2017 21:43:33
There is no knowledge that is not power
Страницы: 1
Наверх