A resposta direta para o uso de DynamicObject no Entity Framework Core é: o EF Core não suporta o uso de DynamicObject ou do tipo dynamic diretamente como entidade ou propriedade de mapeamento.
Isso ocorre porque o C# não consegue gerar Árvores de Expressão (Expression Trees) para tipos dinâmicos, o que impede o LINQ de traduzir suas propriedades para consultas SQL. Além disso, o modelo de metadados do EF Core precisa ser rigidamente tipado em tempo de compilação/inicialização.
No entanto, é totalmente possível atingir exatamente a experiência de ler e escrever propriedades dinâmicas (produto.Preco = 10) no EF Core combinando DynamicObject com a funcionalidade de colunas JSON (nativa a partir do EF Core 7+).
Abaixo está o tutorial completo, do início ao fim, mostrando a arquitetura correta para isso.
Como Funciona a Solução
A entidade armazenará os dados flexíveis dentro de uma string JSON no banco de dados.
Criaremos uma classe derivada de
DynamicObjectque intercepta a leitura (TryGetMember) e a escrita (TrySetMember) de propriedades dinâmicas e sincroniza com o JSON.Usaremos Value Converters do EF Core para converter automaticamente o JSON da tabela do banco de dados na nossa classe dinâmica ao ler/gravar.
Passo 1: Criar a classe DynamicBag (herdada de DynamicObject)
Esta classe gerenciará as propriedades flexíveis como um dicionário de chave-valor na memória e lidará com a serialização/deserialização JSON.
using System.Dynamic;
using System.Text.Json;
public class DynamicBag : DynamicObject
{
// Dicionário interno para armazenar as propriedades dinâmicas
public Dictionary<string, object?> Properties { get; set; } = new();
// Intercepta a leitura de propriedades (ex: var x = entidade.Bag.Preco)
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
return Properties.TryGetValue(binder.Name, out result);
}
// Intercepta a atribuição de propriedades (ex: entidade.Bag.Preco = 99.90)
public override bool TrySetMember(SetMemberBinder binder, object? value)
{
Properties[binder.Name] = value;
return true;
}
// Converte a estrutura para JSON para ser salva no banco
public string ToJson()
{
return JsonSerializer.Serialize(Properties);
}
// Cria uma instância de DynamicBag a partir de uma string JSON vinda do banco
public static DynamicBag FromJson(string json)
{
var bag = new DynamicBag();
if (string.IsNullOrWhiteSpace(json)) return bag;
var rawDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
if (rawDict != null)
{
foreach (var kvp in rawDict)
{
bag.Properties[kvp.Key] = ConvertJsonElement(kvp.Value);
}
}
return bag;
}
// Auxiliar para converter tipos nativos do System.Text.Json em tipos C# primitivos
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.TryGetInt64(out long l) ? l : element.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
_ => element.GetRawText()
};
}
Passo 2: Criar a Entidade do EF Core
Agora crie sua entidade tipada. Note que a propriedade dinâmica Atributos é do tipo dynamic, mas por baixo é instanciada como nosso DynamicBag.
public class Produto
{
public int Id { get; set; }
public string Nome { get; set; } = string.Empty;
// Propriedade dynamic exposta para a aplicação
public dynamic Atributos { get; set; } = new DynamicBag();
}
Passo 3: Configurar o DbContext com Value Converter
No seu DbContext, você precisa instruir o EF Core a converter a classe DynamicBag em uma coluna de texto/JSON no banco de dados usando HasConversion.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
public class AppDbContext : DbContext
{
public DbSet<Produto> Produtos => Set<Produto>();
protected override OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Exemplo usando SQLite (funciona perfeitamente em SQL Server, PostgreSQL, etc.)
optionsBuilder.UseSqlite("Data Source=app.db");
}
protected override OnModelCreating(ModelBuilder modelBuilder)
{
// Criamos o conversor entre DynamicBag e string (JSON)
var converter = new ValueConverter<dynamic, string>(
v => ((DynamicBag)v).ToJson(),
v => DynamicBag.FromJson(v)
);
modelBuilder.Entity<Produto>(entity =>
{
entity.HasKey(p => p.Id);
entity.Property(p => p.Nome).IsRequired();
// Mapeia a propriedade 'Atributos' como coluna texto/json no banco
entity.Property(p => p.Atributos)
.HasConversion(converter)
.HasColumnType("nvarchar(max)"); // Em PostgreSQL use "jsonb", em SQLite use "TEXT"
});
}
}
Passo 4: Exemplo Completo de Uso (Criação, Leitura e Atualização)
Veja como manipular os dados dinâmica e fortemente tipados na aplicação:
using System;
using System.Linq;
class Program
{
static void Main()
{
// 1. Inicializar Banco de Dados
using (var db = new AppDbContext())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
// 2. INSERIR DADOS DINÂMICOS
var produto1 = new Produto
{
Nome = "Smartphone Top"
};
// Adicionando propriedades livremente em tempo de execução
produto1.Atributos.Cor = "Preto";
produto1.Atributos.ArmazenamentoGB = 256;
produto1.Atributos.Possui5G = true;
db.Produtos.Add(produto1);
db.SaveChanges();
Console.WriteLine("Produto salvo com sucesso!");
}
// 3. LER E RECUPERAR DADOS DINÂMICOS
using (var db = new AppDbContext())
{
var produtoBD = db.Produtos.First();
Console.WriteLine($"\nId: {produtoBD.Id}");
Console.WriteLine($"Nome: {produtoBD.Nome}");
// Acessando diretamente como membros estáticos graças ao DynamicObject
Console.WriteLine($"Cor: {produtoBD.Atributos.Cor}");
Console.WriteLine($"Memória: {produtoBD.Atributos.ArmazenamentoGB} GB");
Console.WriteLine($"Suporta 5G: {produtoBD.Atributos.Possui5G}");
// 4. ATUALIZAR PROPRIEDADE DINÂMICA
produtoBD.Atributos.Cor = "Azul Titanium";
produtoBD.Atributos.NovoCampoGenerico = "Adicionado depois";
db.SaveChanges();
}
}
}
Cuidados Importantes e Limitações
Filtros em Consultas LINQ:
Você não pode fazer filtros LINQ diretos nas propriedades do
DynamicObjectcomodb.Produtos.Where(p => p.Atributos.Cor == "Preto"). O C# lançará exceção de Expression Tree.Alternativa no EF Core 8/9+: Se precisar fazer queries dentro do JSON, utilize a API nativa do EF Core
EF.Functions.JsonContainsou consulte as colunas JSON mapeadas diretamente como tipos de propriedade complexa/JSON.
Detecção de Mudanças (Change Tracking):
Ao alterar uma propriedade dentro de
produto.Atributos(ex:produto.Atributos.Cor = "Vermelho"), comoAtributosé um objeto de referência cujo ponteiro não mudou, o EF Core pode não notar automaticamente que ele mudou.Para garantir a persistência em atualizações, marque a propriedade como modificada se necessário:
C#db.Entry(produto).Property(p => p.Atributos).IsModified = true;
No video
Comentários