How to make a .NET API dynamically work with any table in the database without explicitly creating a separate class for each table?

huangapple go评论91阅读模式
英文:

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(&quot;toilets&quot;)]
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(&quot;ward no&quot;)]
public double wardno { get; set; }
public string? localityna { get; set; }
[Column(&quot;code no.&quot;)]
public string? codeno { get; set; }
[Column(&quot;geom&quot;, TypeName = &quot;geometry (point)&quot;)]
public Geometry? Geom { get; set; }
}
}

dbcontext.cs

using Microsoft.EntityFrameworkCore;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace layers
{
public class dbcontext : DbContext
{
public dbcontext(DbContextOptions&lt;dbcontext&gt; options)
: base(options)
{
}
public DbSet&lt;toilets&gt; toilets { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity&lt;toilets&gt;()
.Property(x =&gt; x.Geom)
.HasColumnType(&quot;geometry&quot;);
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&lt;DynamicDbContext&gt; options)
: base(options)
{
}
public DbSet&lt;BaseEntity&gt; 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 =&gt; t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; t.IsSubclassOf(typeof(BaseEntity)));
foreach (var entityType in entityTypes)
{
modelBuilder.Entity(entityType);
}
}
public async Task&lt;IQueryable&lt;BaseEntity&gt;&gt; GetEntities&lt;T&gt;(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 =&gt; e.GetType() != typeof(BaseEntity));
}
// Retrieve data from the specified table
var entityType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t =&gt;
t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; t.IsSubclassOf(typeof(BaseEntity)) &amp;&amp; t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase));
if (entityType == null)
{
try
{
// Debugging: Print available types to check if &quot;states&quot; table is recognized
Console.WriteLine(&quot;Available Types:&quot;);
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
Console.WriteLine(type.Name);
}
}
catch (Exception ex)
{
Console.WriteLine(&quot;Error: &quot; + ex.Message.ToString());
}
}
// Use reflection to invoke the Set&lt;TEntity&gt; 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&lt;BaseEntity&gt;)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库的绝佳机会。

以下是我提供的逐步指南:

  1. 使用NuGet管理器安装Dapper。
  2. 创建与表交互的存储库。

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;
    }
    // 如果需要的话,您可以添加更新、插入和删除方法。
}
  1. 使用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:

  1. Install Dapper with the Nuget Manager
  2. 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 = &quot;Your PostgreSQL Connection string&quot;;

    public async Task&lt;IEnumerable&lt;dynamic&gt;&gt; GetAllAsync(string tableName)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        var query = $&quot;SELECT * FROM {tableName}&quot;;
        var result = await connection.QueryAsync&lt;dynamic&gt;(query);
        return result;
    }

    public async Task&lt;dynamic&gt; GetByIdAsync(string tableName, int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        var query = $&quot;SELECT * FROM {tableName} WHERE id = @Id&quot;;
        var result = await connection.QuerySingleOrDefaultAsync&lt;dynamic&gt;(query, new { Id = id });
        return result;
    }
    // You can add Update, Insert and Delete methods if you want to.
}
  1. 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&lt;string, object&gt;)entity;
    var columnNames = string.Join(&quot;, &quot;, parameters.Keys);
    var paramNames = string.Join(&quot;, &quot;, parameters.Keys.Select(key =&gt; &quot;@&quot; + key));

    var query = $&quot;INSERT INTO {tableName} ({columnNames}) VALUES ({paramNames})&quot;;

    await connection.ExecuteAsync(query, entity);
}

And finally how to use it:

var repository = new ExchangeRepository();
dynamic entity = new ExpandoObject();
entity.Name = &quot;Some Name&quot;;
entity.Age = 30;
await repository.AddAsync(&quot;Person&quot;, entity);

答案2

得分: 0

我在PostgreSQL中使用存储过程和Entity Framework中的数据表解决了这个问题。

英文:

I solved it using stored procedure in PostgreSQL and datatables in Entity Framework.

huangapple
  • 本文由 发表于 2023年7月28日 01:39:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76782244.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定