英文:
How to make a .NET API dynamically work with any table in the database without explicitly creating a separate class for each table?
问题
如何使.NET API动态与数据库中的任何表格一起工作,而无需为每个表格显式创建单独的类?
我尝试使用.NET反射来实现这一点。
我目前正在使用:
.NET
Entity Framework
C#
PostgreSQL
处理GIS数据
目前,我正在开发一个.NET API,在该API中,我最初只处理一个表格。
我有一个名为Toilets.cs的类,用于表示名为toilets的表格架构,并在dbcontext.cs中有其dbcontext
,如下所示: -
Toilets.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(); // 使用NetTopologySuite注册Npgsql
}
base.OnConfiguring(optionsBuilder);
}
}
}
但现在我必须对这个API进行更改,以便当向数据库添加新表格时(即使列数不同但具有几何数据),API应该动态映射它。
我尝试创建一个基本实体并尝试将此BaseEntity
用作DbSet
的通用类型。
还尝试使用.NET反射在运行时动态添加实体配置。
我的尝试看起来有点像这样
DynamicDBcontext.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)
{
// 反射(在运行时动态添加实体配置)
// 为数据库中的每个表格。
// 将每个表格映射到BaseEntity类。
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))
{
// 从所有表格检索所有数据(不包括BaseEntity表格本身)
return Entities.Where(e => e.GetType() != typeof(BaseEntity));
}
// 从指定的表格检索数据
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
{
// 调试:打印可用类型以检查“states”表是否被识别
Console.WriteLine("可用类型:");
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
Console.WriteLine(type.Name);
}
}
catch (Exception ex)
{
Console.WriteLine("错误:" + ex.Message.ToString());
}
}
// 使用反射来使用entityType调用Set<TEntity>方法
var setMethod = typeof(DbContext).GetMethod(nameof(Set), Type.EmptyTypes).MakeGenericMethod(entityType);
var dbSet = setMethod.Invoke(this, null);
// 从指定的表格检索所有数据
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] // 从数据库架构中排除此属性
public string TableName { get; set; }
}
我知道这不会起作用,会引发无效的表格名称错误。
不知道如何实现这一点。
非常感谢您的帮助。
英文:
How to make a .NET API dynamically work with any table in the database without explicitly creating a separate class for each table?
I tried to implement this using .NET reflection.
I am currently using:
.NET
Entity Framework
C#
PostgreSQL
and handling GIS data
Currently I am working on a .NET API in which I was initially handling only one table.
I had a Toilets.cs class for representing table schema of a table named toilets and had its dbcontext
in dbcontext.cs which looked like this:-
Toilets.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);
}
}
}
But now I have to make changes to this API such that when a new table is added to the database (even with different number of columns but having geometry), then the API should dynamically map it.
I tried creating a base entity and tried to use this BaseEntity
as a generic type for DbSet
.
Also, tried using .NET Reflection for Dynamically adding entity configurations for each table at runtime.
My attempt kind of looks like this
DynamicDBcontext.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; }
}
I know it will not work and an Invalid Table Name error is thrown.
Don't know how to implement this.
Help is much appreciated.
答案1
得分: 1
用于数据库交互的Dapper
这听起来是使用Dapper NuGet库的绝佳机会。
以下是我提供的逐步指南:
- 使用NuGet管理器安装Dapper。
- 创建与表交互的存储库。
ExchangeRepository.cs
using Dapper;
using Npgsql;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Threading.Tasks;
public class ExchangeRepository
{
// 变量
const private string _connectionString = "您的PostgreSQL连接字符串";
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;
}
// 如果需要的话,您可以添加更新、插入和删除方法。
}
- 使用
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 = "一些名称";
entity.Age = 30;
await repository.AddAsync("Person", entity);
英文:
Dapper for Database Interaction
This sounds like a perfect opportunity for using the Dapper NuGet Library.
Here is a step-by-step guide from me:
- Install Dapper with the Nuget Manager
- Create a Repository for interaction with the tables
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.
}
- Use
ExpandoObject
to Update or Insert.
Example Use:
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);
}
And finally how to use it:
var repository = new ExchangeRepository();
dynamic entity = new ExpandoObject();
entity.Name = "Some Name";
entity.Age = 30;
await repository.AddAsync("Person", entity);
答案2
得分: 0
我在PostgreSQL中使用存储过程和Entity Framework中的数据表解决了这个问题。
英文:
I solved it using stored procedure in PostgreSQL and datatables in Entity Framework.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论