Хочу поделиться одной идеей, как в 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 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 и выше).
От 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}");
}
}
}
}
}