如何使 .NET API 动态地使用数据库中的任何表,而无需为每个表显式创建单独的类?

问题描述 投票:0回答:1

如何使 .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; }
}

我知道它不会工作,并且会抛出无效的表名称错误。

不知道如何实现。 非常感谢您的帮助。

c# asp.net entity-framework asp.net-core
1个回答
0
投票

Dapper 用于数据库交互

这听起来像是使用 Dapper NuGet 库的绝佳机会。

这是我的分步指南:

  1. 使用 Nuget Manager 安装 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
{
    // 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.
}
  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 = "Some Name";
entity.Age = 30;
await repository.AddAsync("Person", entity);
© www.soinside.com 2019 - 2024. All rights reserved.