[C#][SQL Server] Entity Framework與Stored Procedure搭配,建立可動態產生新Table的RESTFul API!

  • 1773
  • 0

同table一直累加(例如紀錄使用者操作的table),資料到一定數量以上後就算加上索引以空間換時間,還是不免有點"慢"

不論SQL/NoSQL,一般解法就是以日期去拆分Table/Collection

Entity Framework 物件關係對映沒辦法動態產表,就只好搭配Stored Procedure做RESTFul API了

整個流程拆分為三段程式:
1. [Stored Procedure1]:單純透過 SQL 資料定義語言(DDL),建立Table
2. [Stored Procedure2]:資料操作語言(DML)與資料查詢語言(DQL),接收後端傳來的查詢/新增/修改條件並回傳固定Class格式的資料,若特殊條件達成再呼叫[Stored Procedure1]建立新的Table
3. [.Net Core]:將封包查詢條件傳到SQL Server上,接收到資料再回傳給前端

下面程式供參考使用,應需求再自行調整

[Stored Procedure1]

USE [TestDB]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
/*************** DynamicCreateTable **************/
/*功能:依年月產生新Table	  */
/*參數:@yyyymm 年月 公司別   */
/*************************************/

ALTER PROCEDURE [dbo].[DynamicCreateTable] 	
	@yyyymm CHAR(6)	
AS
  DECLARE @tableName CHAR(13)
  DECLARE @createTableCommand varCHAR(1000)
  DECLARE @createClixCommand CHAR(255)
  DECLARE @createIxCommand CHAR(255)
  DECLARE @clustIndexName CHAR(11)
  DECLARE @indexName CHAR(12)
    SELECT @tableName='dbo.Test'+@yyyymm
    SELECT @clustIndexName='CLIX_'+@yyyymm
    SELECT @indexName='IX_'+@yyyymm
   
 SELECT @createTableCommand="CREATE TABLE "+@tableName
				+"(A char (10) NOT NULL DEFAULT(''), B varchar (30) NOT NULL DEFAULT(''),"
	           +"C dec (12,0) NOT NULL DEFAULT(0), D datetime NULL)";
 EXEC (@createTableCommand);
 SELECT @createClixCommand="CREATE UNIQUE  CLUSTERED  INDEX "+@clustIndexName+" ON "+@tableName+" (A,B)";
 EXEC (@createClixCommand);
 SELECT @createIxCommand="CREATE INDEX "+@indexName+" ON "+@tableName+" (C,D)";
 EXEC (@createIxCommand);

[Stored Procedure2]

USE [frudat]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:		Richard
-- 使用範例:
--	exec stk104CRUD @method='post', @tableName='test201902', 
--	@A = 'a',
--	@B ='test',
--	@C = 1.23,
--	@D ='1992-07-19 10:00:00.000'
-- =============================================
ALTER PROCEDURE [dbo].[testTableCRUD]
	@method varchar(6) = 'GET',
	@yyyyMM varchar(13),
	@A char(12) = null,
	@B varchar(30) = null,
	@C decimal(7, 1) = 0,
	@D varchar(30) = null
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

	DECLARE @SqlComm varchar(1000);
	DECLARE @ErrorMessage varchar(1000);

	if (UPPER(@method) = 'GET')
	begin
		set @SqlComm = 'select * from test' + @yyyyMM + ' WHERE 1=1 
						AND (' + ISNULL('''' + @A + '''', 'null') + ' is null OR A Like ' + ISNULL('''' + @A + '''', 'null') + ')';
	end
	else if (UPPER(@method) = 'POST')
	begin
		if (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES  where TABLE_NAME='test'+ @yyyyMM))
		begin
			set @SqlComm = 'exec DynamicCreateTable @yyyymm='+ @yyyyMM;			
			exec(@SqlComm);
		end

		set @SqlComm = 'insert into test' + @yyyyMM + ' (A, B, C, D)
						values('
						+ ISNULL('''' + @A + '''','''''') + ','
						+ ISNULL('''' + @B + '''','null') + ','
						+ CONVERT(varchar(15), @C) + ','
						+ ISNULL('''' + @D + '''','null')
						+ ')';		
	end
	else if (UPPER(@method) = 'PUT')
	begin
		set @SqlComm = 'update test' + @yyyyMM + ' set '
						+ 'B = ' + ISNULL('''' + @B + '''','null') + ','
						+ 'C = ' + CONVERT(varchar(15), @C) + ','
						+ 'D = ' + ISNULL('''' + @D + '''','null')
						+ ' WHERE 1=1 
						AND (' + ISNULL('''' + @A + '''','null') + ' is null OR A Like ' + ISNULL('''' + @A + '''','null') + ')';
	end
	else if (UPPER(@method) = 'DELETE')
	begin
		set @SqlComm = 'delete from test' + @yyyyMM + ' WHERE 1=1 
						AND (' + ISNULL('''' + @A + '''','null') + ' is null OR A Like ' + ISNULL('''' + @A + '''','null') + ')';
	end
	else
	begin;
		THROW 51000, 'ERROR: @method must be GET, POST, PUT or DELETE!', 1;
	end;

	BEGIN TRY
    		exec(@SqlComm);
	END TRY
	BEGIN CATCH
		SELECT	@ErrorMessage = ERROR_MESSAGE() ;
		THROW 51000, @ErrorMessage, 1;
	END CATCH;
	
END

[.Net Core]

public class Test
{
	public string A { get; set; }
	public string B { get; set; }
	public decimal? C { get; set; }
	public DateTime? D { get; set; }
}

public class Request : Test
{
	public string Method { get; set; }
	public string YyyyMM { get; set; }
	public int? Count { get; set; }
}

[HttpPost]
public IActionResult TestTable( [FromBody] Request Request)
{
	if (!ModelState.IsValid)
	{
		return BadRequest(ModelState);
	}

	//預設值調整			
	if (Request.Count == null)
	{
		Request.Count = 100;
	}

	try
	{
		string[] param = new[] {
			Request.Method,
			Request.YyyyMM,
			Request.A, //pk begin
			Request.B,					
			Request.C.ToString(),					
			Request.D == null ? null : Request.D.GetValueOrDefault().ToString("yyyy-MM-dd HH:mm:ss.fff"),
		};

		switch (S03Request.Method.ToUpper())
		{
			case "GET":
				//批量查詢(pk = null)
				if (Request.A == null)
				{							
					var GetTest = _context.S03YM.FromSql("testTableCRUD @method={0}, @yyyyMM={1}", param).Take(Request.Count.GetValueOrDefault());
						return Ok(GetTest);
				}
				//條件查詢
				else
				{							
					IQueryable<Test> GetTest = _context.Test.FromSql("testTableCRUD @method={0}, @yyyyMM={1}, " +
								"@A={2}", param).Take(Request.Count.GetValueOrDefault());
						return Ok(GetTest);
				}
			case "POST":
				int PostTest = _context.Database.ExecuteSqlCommand("EXEC testTableCRUD @method={0}, @yyyyMM={1}, " +
								"@A= {2}, @B= {3}, @C= {4}, @D= {5}", param);
				break;
			case "PUT":
				int PutTest = _context.Database.ExecuteSqlCommand("EXEC testTableCRUD @method={0}, @yyyyMM={1}, " +
								"@A= {2}, @B= {3}, @C= {4}, @D= {5}", param);
				break;
			case "DELETE":
				int DeleteTest = _context.Database.ExecuteSqlCommand("EXEC testTableCRUD @method={0}, @yyyyMM={1}, " +
								"@A={2}", param);
				break;
			default:
				return BadRequest("ERROR: @method must be GET, POST, PUT or DELETE!");
		}
	}
	catch(Exception ex)
	{
		return BadRequest(ex.Message);
	}
		return Ok();
}