如何使 .NET API 动态地使用数据库中的任何表,而无需为每个表显式创建单独的类?
我尝试使用 .NET 反射来实现这一点。
我目前正在使用:
.NET
实体框架
C#
PostgreSQL
和处理 GIS 数据
目前我正在开发一个 .NET API,最初我只处理一个表。 我有一个Toilets.cs类来表示名为toilets的表的表模式,并且在dbcontext.cs中有它的
dbcontext
,如下所示:-
厕所.cs
using NetTopologySuite.Geometries;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace layers
{
[Table("toilets")]
public class toilets
{
[Key]
public int gid { get; set; }
public string? name { get; set; }
public double tessellate { get; set; }
public double extrude { get; set; }
public double visibility { get; set; }
[Column("ward no")]
public double wardno { get; set; }
public string? localityna { get; set; }
[Column("code no.")]
public string? codeno { get; set; }
[Column("geom", TypeName = "geometry (point)")]
public Geometry? Geom { get; set; }
}
}
dbcontext.cs
using Microsoft.EntityFrameworkCore;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace layers
{
public class dbcontext : DbContext
{
public dbcontext(DbContextOptions<dbcontext> options)
: base(options)
{
}
public DbSet<toilets> toilets { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<toilets>()
.Property(x => x.Geom)
.HasColumnType("geometry");
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
var npgsqlOptionsBuilder = new NpgsqlDbContextOptionsBuilder(optionsBuilder);
npgsqlOptionsBuilder.UseNetTopologySuite(); // Register NetTopologySuite with Npgsql
}
base.OnConfiguring(optionsBuilder);
}
}
}
但现在我必须对此 API 进行更改,以便当将新表添加到数据库时(即使列数不同但具有几何形状),API 也应该动态映射它。
我尝试创建一个基本实体,并尝试使用此
BaseEntity
作为 DbSet
的通用类型。
另外,尝试使用 .NET Reflection 在运行时为每个表动态添加实体配置。
我的尝试看起来像这样
动态DBcontext.cs
using Microsoft.EntityFrameworkCore;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace layers
{
public class DynamicDbContext : DbContext
{
public DynamicDbContext(DbContextOptions<DynamicDbContext> options)
: base(options)
{
}
public DbSet<BaseEntity> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// reflection (dynamically add entity configurations)
// for each table in the database at runtime.
// map each table to the BaseEntity class.
var assembly = Assembly.GetExecutingAssembly();
var entityTypes = assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(BaseEntity)));
foreach (var entityType in entityTypes)
{
modelBuilder.Entity(entityType);
}
}
public async Task<IQueryable<BaseEntity>> GetEntities<T>(string tableName = null) where T : BaseEntity
{
if (string.IsNullOrEmpty(tableName))
{
// Retrieve all data from all tables (excluding the BaseEntity table itself)
return Entities.Where(e => e.GetType() != typeof(BaseEntity));
}
// Retrieve data from the specified table
var entityType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t =>
t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(BaseEntity)) && t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase));
if (entityType == null)
{
try
{
// Debugging: Print available types to check if "states" table is recognized
Console.WriteLine("Available Types:");
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
Console.WriteLine(type.Name);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message.ToString());
}
}
// Use reflection to invoke the Set<TEntity> method with the entityType
var setMethod = typeof(DbContext).GetMethod(nameof(Set), Type.EmptyTypes).MakeGenericMethod(entityType);
var dbSet = setMethod.Invoke(this, null);
// Retrieve all data from the specified table
return await Task.FromResult(((IQueryable<BaseEntity>)dbSet).AsQueryable());
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
var npgsqlOptionsBuilder = new NpgsqlDbContextOptionsBuilder(optionsBuilder);
npgsqlOptionsBuilder.UseNetTopologySuite();
}
base.OnConfiguring(optionsBuilder);
}
}
}
BaseEntity.cs
using System.ComponentModel.DataAnnotations.Schema;
public abstract class BaseEntity
{
[NotMapped] // Exclude this property from the database schema
public string TableName { get; set; }
}
我知道它不会工作,并且会抛出无效的表名称错误。
不知道如何实现。 非常感谢您的帮助。
这听起来像是使用 Dapper NuGet 库的绝佳机会。
这是我的分步指南:
ExchangeRepository.cs
using Dapper;
using Npgsql;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Threading.Tasks;
public class ExchangeRepository
{
// Variables
const private string _connectionString = "Your PostgreSQL Connection string";
public async Task<IEnumerable<dynamic>> GetAllAsync(string tableName)
{
using var connection = new NpgsqlConnection(_connectionString);
var query = $"SELECT * FROM {tableName}";
var result = await connection.QueryAsync<dynamic>(query);
return result;
}
public async Task<dynamic> GetByIdAsync(string tableName, int id)
{
using var connection = new NpgsqlConnection(_connectionString);
var query = $"SELECT * FROM {tableName} WHERE id = @Id";
var result = await connection.QuerySingleOrDefaultAsync<dynamic>(query, new { Id = id });
return result;
}
// You can add Update, Insert and Delete methods if you want to.
}
ExpandoObject
更新或插入。使用示例:
public async Task AddAsync(string tableName, ExpandoObject entity)
{
using var connection = new NpgsqlConnection(_connectionString);
var parameters = (IDictionary<string, object>)entity;
var columnNames = string.Join(", ", parameters.Keys);
var paramNames = string.Join(", ", parameters.Keys.Select(key => "@" + key));
var query = $"INSERT INTO {tableName} ({columnNames}) VALUES ({paramNames})";
await connection.ExecuteAsync(query, entity);
}
最后是如何使用它:
var repository = new ExchangeRepository();
dynamic entity = new ExpandoObject();
entity.Name = "Some Name";
entity.Age = 30;
await repository.AddAsync("Person", entity);