I am looking for ways to do LINQ on a table selected in runtime via string variable.
This is what I have so far using reflection:
private Entities ctx = new Entities();
public List<AtsPlatform> GetAtsPlatformByName(string atsPlatformName)
{
List<AtsPlatform> atsPlatform = null;
System.Reflection.PropertyInfo propertyInfo = ctx.GetType().GetProperty(atsPlatformName.ToLower());
var platform = propertyInfo.GetValue(ctx, null);
// it fails here highlighting "platform" with error that reads "Error 1 Could not find an implementation of the query pattern for source type 'System.Data.Objects.ObjectQuery'. 'Select' not found. Consider explicitly specifying the type of the range variable 'ats'."
atsPlatform = ((from ats in platform select new AtsPlatform { RequestNumber = ats.RequestNumber, NumberOfFail = ats.NumberOfFail, NumberOfFailWithCR = ats.NumberOfFailWithCR, NumberOfTestCase = ats.NumberOfTestCase }).ToList());
return atsPlatform;
}
In my model class, I have:
public class AtsPlatform
{
public string Name { get; set; }
public string RequestNumber { get; set; }
public Int32? NumberOfFail { get; set; }
public Int32? NumberOfTestCase { get; set; }
public Int32? NumberOfFailWithCR { get; set; }
}
In Database, I have the following tables: "ats1", "ats2", "ats3" .. "atsN" where each of them has the same entity fields as the properties defined in "AtsPlatform"
What I would like to do is simply:
List<AtsPlatform> a1 = GetAtsPlatformByName("ats1");
List<AtsPlatform> a2 = GetAtsPlatformByName("ats2");
List<AtsPlatform> aN = GetAtsPlatformByName("atsN");
I could use "switch" but this makes the code less expandable and requires update whenever new "ats(N+1)" gets created.
My 2 days of research lead me nowhere but back to ground zero. I'm quite stuck.
PLEASE HELP! Thanks!
Instead of reflection, how about using the SqlQuery function?
So
List<AtsPlatform> GetAtsPlatformByName(int index)
{
using (var ctx = new Entities())
{
return ctx.Database.SqlQuery<AtsPlatform>("SELECT * FROM dbo.ats" + index)
.ToList();
}
}
Also, there is no change tracking on the entities using the SqlQuery method on the Database object (which is ok in your case I suppose since the AtsPlatform class only contains primitive properties).
For changes tracking you will need to use the DbSet SqlQuery method, and may need to mix some reflection in.
Sorry for my late response as I wondered off trying out different solutions:
Solution #1: Master Table
As suggested by #Alexw, creating a Master Table works the best ONLY if you are allowed to change the design of the db. I'm currently working with the db owner to make this change. Due to dependencies, this change has to wait till next phase.
Meanwhile, I've created mock db to exercise this approach.
Solution #2: Raw Query
As Suggested by #Umair, raw query will do the job. I've created a class that handles raw sql query.
public class AtsRawQuery
{
private string ConnetionString = "";
public AtsRawQuery(string connectionString)
{
this.ConnetionString = connectionString;
}
public List<List<string>> Query(string queryString)
{
List<List<string>> results = null;
MySqlConnection conn = null;
MySqlDataReader rdr = null;
try
{
conn = new MySqlConnection(this.ConnetionString);
conn.Open();
MySqlCommand cmd = new MySqlCommand(queryString, conn);
rdr = cmd.ExecuteReader();
if (rdr.HasRows)
{
results = new List<List<string>>();
while (rdr.Read())
{
List<string> curr_result = new List<string>();
for (int columnIndex = 0; columnIndex <= rdr.FieldCount - 1; columnIndex++)
{
curr_result.Add(rdr.GetString(columnIndex));
}
results.Add(curr_result);
}
}
}
catch (MySqlException ex)
{
Console.WriteLine(ex.Message);
return null;
}
finally
{
if (rdr != null)
{
rdr.Close();
}
if (conn != null)
{
conn.Close();
}
}
return results;
}
}
This class returns a 2 dimension list for later consumption.
In my model class, I added a parser method:
public class AtsPlatform
{
public string Name { get; set; }
public string RequestNumber { get; set; }
public Int32? NumberOfFail { get; set; }
public Int32? NumberOfTestCase { get; set; }
public Int32? NumberOfFailWithCR { get; set; }
public void Parse(string name, string requestNumber, string numberOfFail, string numberOfTestCase, string numberOfFailWithCR)
{
Int32 temp;
this.Name = name;
this.RequestNumber = requestNumber;
this.NumberOfFail = (Int32.TryParse(numberOfFail, out temp)) ? Int32.Parse(numberOfFail) : 0;
this.NumberOfTestCase = (Int32.TryParse(numberOfTestCase, out temp)) ? Int32.Parse(numberOfTestCase) : 0;
this.NumberOfFailWithCR = (Int32.TryParse(numberOfFailWithCR, out temp)) ? Int32.Parse(numberOfFailWithCR) : 0;
}
}
Solution #2(b): Raw Query using ExecuteStoreCommand
public List<AtsPlatform> GetAtsPlatformByName(string atsPlatformName)
{
List<AtsPlatform> atsPlatforms = null;
string stm = String.Format("SELECT RequestNumber, NumberOfFail, NumberOfTestCase, NumberOfFailWithCR FROM {0}", atsPlatformName);
atsPlatforms = new List<AtsPlatform>();
foreach (AtsPlatform ats in ctx.ExecuteStoreQuery<AtsPlatform>(stm))
{
atsPlatforms.Add(ats);
}
return atsPlatforms;
}
Solution #3: Stored Procedure
I've created a stored procedure and here is the code:
DELIMITER $$
CREATE PROCEDURE `UnionAtsTables`()
BEGIN
DECLARE atsName VARCHAR(10);
DECLARE atsIndex INT;
SET atsIndex = 1;
SET #qry = '';
WHILE atsIndex > 0 DO
SET atsName =concat('ATS',atsIndex);
IF sf_is_table(atsName) = 1 THEN
Set #temp_qry = CONCAT('SELECT *, ''', atsName ,''' As TestPlatform FROM ', atsName, ' WHERE RequestNumber <> ''''' );
If #qry = '' THEN
SET #qry = #temp_qry;
ELSE
SET #qry = CONCAT(#qry, ' UNION ', #temp_qry);
END IF;
ELSE
SET atsIndex = -1;
END IF;
SET atsIndex = atsIndex + 1;
END WHILE;
DROP TABLE IF EXISTS ats_all;
SET #CreateTempTableQuery = CONCAT('CREATE TEMPORARY TABLE ats_all AS ', #qry ,'');
PREPARE stmt1 FROM #CreateTempTableQuery;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
ALTER TABLE ats_all DROP COLUMN ExecOrder;
ALTER TABLE ats_all ADD ExecOrder INT PRIMARY KEY AUTO_INCREMENT;
ALTER TABLE ats_all auto_increment = 0;
END
Here is the function I found online that checks if table exists in db.
DELIMITER $$
CREATE FUNCTION `sf_is_table`(`in_table` varchar(255)) RETURNS tinyint(4)
BEGIN
/**
* Check if table exists in database in use
*
* #name sf_is_table
* #author Shay Anderson 08.13 <http://www.shayanderson.com>
*
* #param in_table (table name to check)
* #return TINYINT (1 = table exists, 0 = table does not exist)
*/
# table exists flag
DECLARE is_table BOOLEAN DEFAULT FALSE;
# table count
DECLARE table_count INT DEFAULT 0;
# database name
SET #db = NULL;
# set database name
SELECT
DATABASE()
INTO
#db;
# check for valid database and table names
IF LENGTH(#db) > 0 AND LENGTH(in_table) > 0 THEN
# execute query to check if table exists in DB schema
SELECT COUNT(1) INTO table_count
FROM information_schema.`TABLES`
WHERE TABLE_SCHEMA = #db
AND TABLE_NAME = in_table;
# set if table exists
IF table_count > 0 THEN
SET is_table = TRUE;
END IF;
END IF;
RETURN is_table;
END
Conclusion:
Thank you everyone for your suggestions.
I decided to use Solution #2 since it does not cause as much impact to the db performance as Solution #3 and it does not require db redesign as Solution #1.
I don't think what you are doing will work like that. You should create an entity based on a single 'master' table, eg. Ats
Once you have done this you will have a property on your Entities class called Ats. You can now use this property to select the entities using a raw SQL query like this.
var atsName = "ats1";
using (var context = new Entities())
{
var blogs = context.Ats.SqlQuery(string.Format("SELECT * FROM {0}", atsName)).ToList();
}
Alternatively you could try this (I am assuming the property type is DBSet as you haven't specified it in the question)
var platform = propertyInfo.GetValue(ctx, null) as DBSet<Ats>;
atsPlatform = platform.Select(ats => new A new AtsPlatform { RequestNumber = ats.RequestNumber, NumberOfFail = ats.NumberOfFail, NumberOfFailWithCR = ats.NumberOfFailWithCR, NumberOfTestCase = ats.NumberOfTestCase }).ToList();
return atsPlatform;
Related
I'm trying to send a List Object from my C# WebService method over to my stored procedure in Oracle.
Before posting here, I've tried all suggested duplicate links. Here's what I've accomplished so far:
Success: In C#, I can pass my List values from my HTML page over to my WebService method.
Success: In Oracle, I have created a Table, Object Type, Table Type, and Stored Procedure to accept the List values. I was able to test this using an Anonymous block and sample data.
Problem: I cannot get to pass my List values from my C# WebMethod over to my Oracle Stored Procedure.
I'm currently using the following setup:
Visual Studio 2017
.NET Framework 4.6.1
Oracle.ManagedDataAccess 18.6.0
Keep in mind that the version of Oracle.ManagedDataAccess 18.6.0 does NOT contain the OracleDbType.Array as suggested in the older examples.
public class Automobile
{
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string Country { get; set; }
}
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string InsertCars(List<Automobile> myCars, int userID)
{
DataSet dataSet = new DataSet();
using (OracleConnection sqlConnection = new OracleConnection(OracleDBConnection))
{
using (OracleCommand sqlCommand = new OracleCommand("sp_InsertCars", sqlConnection))
{
sqlConnection.Open();
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.Add(
new OracleParameter
{
CollectionType = OracleCollectionType.PLSQLAssociativeArray,
Direction = ParameterDirection.Input,
ParameterName = "p_CarList",
UdtTypeName = "tt_Automobile",
Size = myCars.Count,
Value = myCars.ToArray()
}
);
sqlCommand.Parameters.Add(
new OracleParameter
{
OracleDbType = OracleDbType.Int32,
Direction = ParameterDirection.Input,
ParameterName = "p_UserID",
Value = userID
}
);
sqlCommand.Parameters.Add(
new OracleParameter
{
OracleDbType = OracleDbType.RefCursor,
Direction = ParameterDirection.Output,
ParameterName = "o_Cursor"
}
);
using (OracleDataAdapter sqlAdapter = new OracleDataAdapter(sqlCommand))
{
sqlAdapter.SelectCommand = sqlCommand;
sqlAdapter.Fill(dataSet);
}
}
return JsonConvert.SerializeObject(dataSet);
}
}
CREATE TABLE tblCars
(
RecordID INT GENERATED BY DEFAULT AS IDENTITY NOMINVALUE NOMAXVALUE INCREMENT BY 1 START WITH 1 NOCACHE NOCYCLE NOORDER,
Make NVARCHAR2(100) NULL,
Model NVARCHAR2(100) NULL,
Year NVARCHAR2(4) NULL,
Country NVARCHAR2(100) NULL,
UserID INT NULL
);
CREATE OR REPLACE TYPE ot_Automobile AS OBJECT
(
Make varchar2(100),
Model varchar2(100),
Year varchar2(4),
Country varchar2(100)
);
CREATE OR REPLACE TYPE tt_Automobile AS TABLE OF ot_Automobile;
CREATE OR REPLACE PROCEDURE sp_InsertCars
(
p_CarList In tt_Automobile,
p_UserID In integer,
o_Cursor Out Sys_RefCursor
)
AS
BEGIN
DBMS_Output.Enable;
For RowItem In (Select * From Table(p_CarList))
Loop
Insert Into tblCars
(
Make,
Model,
Year,
Country,
UserID
)
Values(
RowItem.Make,
RowItem.Model,
RowItem.Year,
RowItem.Country,
p_UserID
);
End Loop;
-- Return our results after insert
Open o_Cursor For
Select Make, Model, Year, Country From tblCars Where UserID = p_UserID;
EXCEPTION
When Others Then
DBMS_Output.Put_Line('SQL Error: ' || SQLERRM);
END sp_InsertCars;
COMMIT
/
The result should allow me to pass my array Object from my WebService WebMethod over to my Oracle stored procedure and then loop through each item of the array to perform an Insert.
Here's an example of the data I'm trying to pass in.
this answer depends on commercial package, but if you're as desperate as i am, it's a lifesaver for very reasonable $300 (circa 2020-Q4)... scouts honor, i'm no shill
DevArt's Oracle provider makes elegant work of passing lists of objects to stored procs... it really works... .net core 3.1 compatible, tested on linux, no dependencies on native oracle client ... see my take on a working console app sample below based on the linked forum post
DevArt's "OracleType.GetObjectType()" API makes the UDT marshalling part of this incredibly trivial for us... way less to grok than the existing unmanaged ODP table type support samples i've seen out there for years
strategic consideration - if you already have a sizable code base on an oracle provider, consider just leaving all that code as-is and only take on regression testing this new dependency where the specialized table type support is actually needed
short and sweet sample for quick digestion
using System;
using System.Data;
using Devart.Data.Oracle;
namespace ConsoleApp1
{
class Program
{
private static int oraTable;
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//good docs:
//direct connection: https://www.devart.com/dotconnect/oracle/docs/StoredProcedures-OracleCommand.html
//linux licensing: https://www.devart.com/dotconnect/oracle/docs/?LicensingStandard.html
using OracleConnection db = new OracleConnection("{insert yours}");
//devart trial licensing nutshell... on WINDOWS, download & run their installer...
//the only thing you really need from that install is the license key file dropped here:
// %programdata%\DevArt\License\Devart.Data.Oracle.key
// then just go nuget the "Devart.Data.Oracle" package reference
//on Windows, the trial license file gets automatically picked up by their runtime
//if you're on Linux, basically just read their good instructions out there
//i've just tested it on WSL so far and plan to try on Azure linux app svc within next day
//db.ConnectionString = "license key=trial:Devart.Data.Oracle.key;" + db.ConnectionString;
db.Direct = true; //nugget: crucial!! https://www.devart.com/dotconnect/oracle/docs/DirectMode.html
db.Open();
var cmd = db.CreateCommand("UserPermissions_u", CommandType.StoredProcedure);
cmd.DeriveParameters();
//tblParm.OracleDbType = OracleDbType.Table;
//passing "table" type proc parm example: https://forums.devart.com/viewtopic.php?t=22243
var obj = new OracleObject(OracleType.GetObjectType("UserPerm", db));
var tbl = new OracleTable(OracleType.GetObjectType("UserPerms", db));
obj["UserPermissionId"] = "sR1CKjKYSKvgU90GUgqq+w==";
obj["adv"] = 1;
tbl.Add(obj);
cmd.Parameters["IN_Email"].Value = "banderson#kingcounty.gov";
cmd.Parameters["IN_Permissions"].Value = tbl;
cmd.ExecuteNonQuery();
//"i can't believe it's not butter!" -me, just now =)
}
}
}
corresponding oracle db definitions:
create or replace type UserPerm as object ( UserPermissionId varchar2(24), std number(1), adv number(1) );
create or replace type UserPerms as table of UserPerm;
create or replace PROCEDURE UserPermissions_u (
IN_Email IN varchar2,
IN_Permissions IN UserPerms
) is
dummyvar number default 0;
begin
select count(*) into dummyvar from table(IN_Permissions);
end;
/
more elaborate shot at generically reflecting on an inbound object to hydrate the proc parms like the OP's request... take with caution, needs testing/bullet-proofing ... i'd love to get better alternatives if anybody cares to share
using System;
using System.Data;
using Devart.Data.Oracle;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
namespace ConsoleApp1
{
public class User
{
public string Email { get; set; }
public List<UserPermissionEffective> Permissions { get; set; }
}
public class UserPermissionEffective
{
public string UserPermissionId { get; set; }
public string Email { get; set; }
public bool Std { get; set; }
public bool Adv { get; set; }
public string Mod { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var dto = new User { Email = "testy#mctesterson.com", Permissions = new List<UserPermissionEffective> {
new UserPermissionEffective { UserPermissionId = "1", Std = false, Adv = true },
new UserPermissionEffective { UserPermissionId = "2", Std = true, Adv = false }
} };
if (dto == null) return;
//good docs:
//direct connection: https://www.devart.com/dotconnect/oracle/docs/StoredProcedures-OracleCommand.html
//linux licensing: https://www.devart.com/dotconnect/oracle/docs/?LicensingStandard.html
var dbstring = Environment.GetEnvironmentVariable("dbstring");
using OracleConnection db = new OracleConnection(dbstring);
db.ConnectionString = "license key=trial:Devart.Data.Oracle.key;" + db.ConnectionString;
db.Direct = true; //nugget: crucial!! https://www.devart.com/dotconnect/oracle/docs/DirectMode.html
db.Open();
var cmd = db.CreateCommand("UserPermissions_u", CommandType.StoredProcedure);
cmd.DeriveParameters();
//regex gets everything following the last underscore. e.g. INOUT_PARMNAME yields PARMNAME
var regex = new Regex(#"([^_\W]+)$", RegexOptions.Compiled);
//get the inboud model's root properties
var dtoProps = dto.GetType().GetProperties();
//loop over all parms assigning model properties values by name
//going by parms as the driver versus object properties to favor destination over source
//since we often ignore some superfluous inbound properties
foreach (OracleParameter parm in cmd.Parameters)
{
var cleanParmName = regex.Match(parm.ParameterName).Value.ToUpper();
var dtoPropInfo = dtoProps.FirstOrDefault(prop => prop.Name.ToUpper() == cleanParmName);
//if table type, then drill into the nested list
if (parm.OracleDbType == OracleDbType.Table)
{
//the type we're assigning from must be a list
//https://stackoverflow.com/questions/4115968/how-to-tell-whether-a-type-is-a-list-or-array-or-ienumerable-or/4115970#4115970
Assert.IsTrue(typeof(IEnumerable).IsAssignableFrom(dtoPropInfo.PropertyType));
var listProperty = (dtoPropInfo.GetValue(dto) as IEnumerable<Object>).ToArray();
//don't bother further logic if the list is empty
if (listProperty.Length == 0) return;
//get the oracle table & item Udt's to be instanced and hydrated from the inbound dto
var tableUdt = OracleType.GetObjectType(parm.ObjectTypeName, db);
var itemUdt = OracleType.GetObjectType(tableUdt.ItemObjectType.Name, db);
var dbList = new OracleTable(tableUdt);
//and the internal list item objects
var subPropInfos = dtoPropInfo.PropertyType.GenericTypeArguments[0].GetProperties().ToDictionary(i=>i.Name.ToUpper(), i=>i);
//for every item passed in...
foreach (var dtoSubItem in listProperty) {
//create db objects for every row of data we want to send
var dbObj = new OracleObject(itemUdt);
//and map the properties from the inbound dto sub items to oracle items by name
//using reflection to enumerate the properties by name
foreach (OracleAttribute field in itemUdt.Attributes)
{
var val = subPropInfos[field.Name.ToUpper()].GetValue(dtoSubItem);
//small tweak to map inbound booleans to 1's & 0's on the db since oracle doesn't support boolean!?!
var isDbBool = field.DbType == OracleDbType.Integer && field.Precision == 1;
dbObj[field] = isDbBool ? ((bool)val ? 1 : 0) : val;
}
//lastly add the db obj to the db table
dbList.Add(dbObj);
}
parm.Value = dbList;
}
else {
parm.Value = dtoPropInfo.GetValue(dto);
}
}
cmd.ExecuteNonQuery();
}
}
}
Please refer following link to setup ODAC
Setup Ref and use follwing link to get the ODAC
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
using System;
using System.Data;
namespace Strace_CustomTypes
{
class Program
{
static void Main(string[] args)
{
// Setup Ref - https://o7planning.org/en/10509/connecting-to-oracle-database-using-csharp-without-oracle-client
// ODAC 64bit ODAC122010Xcopy_x64.zip - https://www.oracle.com/technetwork/database/windows/downloads/index-090165.html
// .Net Framework 4
// 'Connection string' to connect directly to Oracle.
string connString = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=0.0.0.0)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=SIT)));Password=PASSWORD;User ID=USERID";
OracleConnection straceOracleDBConn = new OracleConnection(connString);
OracleCommand cmd = new OracleCommand("PKG_TEMP.TEST_ARRAY", straceOracleDBConn);
cmd.CommandType = CommandType.StoredProcedure;
try
{
straceOracleDBConn.Open();
CustomVarray pScanResult = new CustomVarray();
pScanResult.Array = new string[] { "hello", "world" };
OracleParameter param = new OracleParameter();
param.OracleDbType = OracleDbType.Array;
param.Direction = ParameterDirection.Input;
param.UdtTypeName = "USERID.VARCHAR2_ARRAY";
param.Value = pScanResult;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
}
finally
{
straceOracleDBConn.Close();
cmd.Dispose();
straceOracleDBConn.Dispose();
}
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
}
//Ref https://www.codeproject.com/Articles/33829/How-to-use-Oracle-11g-ODP-NET-UDT-in-an-Oracle-Sto
public class CustomVarray : IOracleCustomType, INullable
{
[OracleArrayMapping()]
public string[] Array;
private OracleUdtStatus[] m_statusArray;
public OracleUdtStatus[] StatusArray
{
get
{
return this.m_statusArray;
}
set
{
this.m_statusArray = value;
}
}
private bool m_bIsNull;
public bool IsNull
{
get
{
return m_bIsNull;
}
}
public static CustomVarray Null
{
get
{
CustomVarray obj = new CustomVarray();
obj.m_bIsNull = true;
return obj;
}
}
public void FromCustomObject(OracleConnection con, IntPtr pUdt)
{
OracleUdt.SetValue(con, pUdt, 0, Array, m_statusArray);
}
public void ToCustomObject(OracleConnection con, IntPtr pUdt)
{
object objectStatusArray = null;
Array = (string[])OracleUdt.GetValue(con, pUdt, 0, out objectStatusArray);
m_statusArray = (OracleUdtStatus[])objectStatusArray;
}
}
[OracleCustomTypeMapping("USERID.VARCHAR2_ARRAY")]
public class CustomVarrayFactory : IOracleArrayTypeFactory, IOracleCustomTypeFactory
{
public Array CreateArray(int numElems)
{
return new string[numElems];
}
public IOracleCustomType CreateObject()
{
return new CustomVarray();
}
public Array CreateStatusArray(int numElems)
{
return new OracleUdtStatus[numElems];
}
}
}
This is my stored procedure, a bit complex
ALTER PROCEDURE [dbo].[spx_SELECT_Etiquetas]
#Localizacao nvarchar(20),
#LocalizacaoID int = NULL,
#Armazem int
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL NVARCHAR(MAX);
IF(#Armazem = 1)
BEGIN
SET #SQL = 'SELECT Etiqueta, xl.LocalizacaoID
FROM OPENQUERY(MACPAC, ''SELECT ET0101 AS Etiqueta,
ET0109 AS Localizacao
FROM (
SELECT DISTINCT FET001.ET0109, FET001.ET0101
FROM AUTO.D805DATPOR.FET001 FET001
WHERE (FET001.ET0104=''''POE'''') AND (FET001.ET0105=''''DIS'''') AND FET001.ET0109 = (''''' + #Localizacao + ''''')
) AS AUTO '') AS mc
LEFT JOIN xLocalizacao xl
ON xl.Localizacao = mc.Localizacao
WHERE LocalizacaoID = ' + CONVERT(NVARCHAR(MAX),#LocalizacaoID) + '
UNION
SELECT Etiqueta, xe.LocalizacaoID FROM xEtiquetas xe
WHERE xe.LocalizacaoID = ' + CONVERT(NVARCHAR(MAX),#LocalizacaoID) + '
ORDER BY Etiqueta DESC';
END
ELSE IF(#Armazem = 2)
BEGIN
SET #SQL = 'SELECT Etiqueta, xl.LocalizacaoID
FROM OPENQUERY(MACPAC, ''SELECT LBLBNB AS Etiqueta,
GHNEMP AS Localizacao
FROM (
SELECT DISTINCT GA160H.GHNEMP, GA160H.LBLBNB
FROM D805DATPOR.GA160H GA160H, D805DATPOR.GA160M GA160M
WHERE GA160M.LBLBNB = GA160H.LBLBNB AND (GA160H.GHZORI=''''CORRICA'''') AND (GA160H.GHCSTA=''''DIS'''') AND GA160H.GHNEMP = (''''' + #Localizacao + ''''')
) AS AUTO '') AS mc
LEFT JOIN xLocalizacao xl
ON xl.Localizacao = mc.Localizacao
WHERE LocalizacaoID = ' + CONVERT(NVARCHAR(MAX),#LocalizacaoID) + '
UNION
SELECT Etiqueta, xe.LocalizacaoID FROM xEtiquetas xe
WHERE xe.LocalizacaoID = ' + CONVERT(NVARCHAR(MAX),#LocalizacaoID) + '
ORDER BY Etiqueta DESC';
END
EXEC sp_executesql #SQL
END
It gets Etiquetas from a local table and also from a table in a linked server.
The Output is the columns Etiquetas and Localizacao
However i have a table to save locally new Etiquetas that are added so i have the following class generated by EF
public partial class xEtiquetas
{
public int EtiquetaID { get; set; }
public int Etiqueta { get; set; }
public int LocalizacaoID { get; set; }
public virtual xLocalizacao xLocalizacao { get; set; }
}
this is my get controller. Basically the relation is
Inventarios > Localizacoes > Etiquetas
the x is just a prefix identifier cause there are different applications using the same database by other coworkers separated by a prefix.
Also I have to get the Etiquetas separately using the stored procedure since it's coming from a linked server. (Unless there's another way)
public IQueryable<InventarioModel> Get()
{
InventarioCiclicoContext db = new InventarioCiclicoContext();
var inventarios = db.xInventario.Select(
i => new InventarioModel {
InventarioID = i.InventarioID,
Colaborador = i.xColaborador.Nome,
Armazem = i.xArmazem.Armazem,
Estado = i.EstadoInventario,
Data = i.DataCriacao,
Localizacoes = i.xLocalizacao.Select(
l => new LocalizacaoModel {
LocalizacaoID = l.LocalizacaoID,
Localizacao = l.Localizacao,
EtiquetasPorInventariar = l.EtiquetasPorInventariar,
EtiquetasInventariadas = l.EtiquetasInventariadas,
Valid = l.IsValid,
Precisao = l.Precisao,
InventarioID = l.InventarioID
}).ToList()
});
string localizacao = ".MP3-D16";
int? localizacaoID = 1;
int? armazem = 1;
var etiquetas = db.spx_SELECT_Etiquetas(localizacao, localizacaoID, armazem);
return inventarios.ToList().AsQueryable();
}
The SP returns -1. I tried using Entity return type but it does not work cause the local table generated is only for the created so the columns returned from the linked server won't make me able to use this.
I created an EtiquetaModel class
public class EtiquetaModel
{
public int Etiqueta { get; set; }
public string Localizacao { get; set; }
}
Is there a way to get the output from the SP to this class? I can't even use ToList() on it.
When using the C# code below to construct a DB2 SQL query the result set only has one row. If I manually construct the "IN" predicate inside the cmdTxt string using string.Join(",", ids) then all of the expected rows are returned. How can I return all of the expected rows using the db2Parameter object instead of building the query as a long string to be sent to the server?
public object[] GetResults(int[] ids)
{
var cmdTxt = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #ids ) ";
var db2Command = _DB2Connection.CreateCommand();
db2Command.CommandText = cmdTxt;
var db2Parameter = db2Command.CreateParameter();
db2Parameter.ArrayLength = ids.Length;
db2Parameter.DB2Type = DB2Type.DynArray;
db2Parameter.ParameterName = "#ids";
db2Parameter.Value = ids;
db2Command.Parameters.Add(db2Parameter);
var results = ExecuteQuery(db2Command);
return results.ToArray();
}
private object[] ExecuteQuery(DB2Command db2Command)
{
_DB2Connection.Open();
var resultList = new ArrayList();
var results = db2Command.ExecuteReader();
while (results.Read())
{
var values = new object[results.FieldCount];
results.GetValues(values);
resultList.Add(values);
}
results.Close();
_DB2Connection.Close();
return resultList.ToArray();
}
You cannot send in an array as a parameter. You would have to do something to build out a list of parameters, one for each of your values.
e.g.: SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #id1, #id2, ... #idN )
And then add the values to your parameter collection:
cmd.Parameters.Add("#id1", DB2Type.Integer).Value = your_val;
Additionally, there are a few things I would do to improve your code:
Use using statements around your DB2 objects. This will automatically dispose of the objects correctly when they go out of scope. If you don't do this, eventually you will run into errors. This should be done on DB2Connection, DB2Command, DB2Transaction, and DB2Reader objects especially.
I would recommend that you wrap queries in a transaction object, even for selects. With DB2 (and my experience is with z/OS mainframe, here... it might be different for AS/400), it writes one "accounting" record (basically the work that DB2 did) for each transaction. If you don't have an explicit transaction, DB2 will create one for you, and automatically commit after every statement, which adds up to a lot of backend records that could be combined.
My personal opinion would also be to create a .NET class to hold the data that you are getting back from the database. That would make it easier to work with using IntelliSense, among other things (because you would be able to auto-complete the property name, and .NET would know the type of the object). Right now, with the array of objects, if your column order or data type changes, it may be difficult to find/debug those usages throughout your code.
I've included a version of your code that I re-wrote that has some of these changes in it:
public List<ReturnClass> GetResults(int[] ids)
{
using (var conn = new DB2Connection())
{
conn.Open();
using (var trans = conn.BeginTransaction(IsolationLevel.ReadCommitted))
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
var parms = new List<string>();
var idCount = 0;
foreach (var id in ids)
{
var parm = "#id" + idCount++;
parms.Add(parm);
cmd.Parameters.Add(parm, DB2Type.Integer).Value = id;
}
cmd.CommandText = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( " + string.Join(",", parms) + " ) ";
var resultList = new List<ReturnClass>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var values = new ReturnClass();
values.Id = (int)reader["ID"];
values.Col1 = reader["COL1"].ToString();
values.Col2 = reader["COL2"].ToString();
resultList.Add(values);
}
}
return resultList;
}
}
}
public class ReturnClass
{
public int Id;
public string Col1;
public string Col2;
}
Try changing from:
db2Parameter.DB2Type = DB2Type.DynArray;
to:
db2Parameter.DB2Type = DB2Type.Integer;
This is based on the example given here
I have a model-first EF model. I just imported the first stored procedure: cpas_POIDVendorProjectDate
I imported it as a function. It has three input parameters: #ProjectID(int), #VendorID(int), and #Workdate(datetime), and returns #POID(int).
Here's the SQL code:
CREATE PROCEDURE [dbo].[cpas_POIDVendorProjectDate]
#VendorID int,
#ProjectID int,
#WorkDate datetime,
#PO_ID int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #RowCount int;
SELECT #PO_ID = ID FROM tblPO WHERE
VendorID = #VendorID
AND ExpirationDate >= #WorkDate
AND (ProjectID IS NULL OR ProjectID = #ProjectID)
AND CapitalExpense = (
SELECT CapitalExpense FROM tblProjects WHERE ID=#ProjectID)
AND GroupCode in (1,3,5);
SET #RowCount = ##RowCount;
IF (#RowCount != 1)
SET #PO_ID = -1*#RowCount;
END
I called it in my c# program as follows:
context.cpas_POIDVendorProjectDate(
currVendorID, currProjectID, currWorkDate, currPOID);
Intellisense says my use of "context" is wrong...It's a "variable", and I'm using it as a "method".
In addition, currPOID is rejected because it's looking for a system.data.objects.OjbectParameter, not an int. Intellisense is happy with the function name and other parameters (strangely...)
What am I doing wrong here?
You can always do this if nothing else works:
using(var context = new MyDataContext())
{
using(var cmd = context.Database.Connection.CreateCommand())
{
cmd.CommandText = "cpas_POIDVendorProjectDate";
cmd.CommandType = CommandType.StoredProcedure;
//if the stored proc accepts params, here is where you pass them in
cmd.Parameters.Add(new SqlParameter("VendorId", 10));
cmd.Parameters.Add(new SqlParameter("ProjectId", 12));
cmd.Parameters.Add(new SqlParameter("WorkDate", DateTimw.Now));
var poid = (int)cmd.ExecuteScalar();
}
}
If you would like an object orientated way, then Mindless passenger has a project that allows you to call a stored proc from entity frame work like this....
using (testentities te = new testentities())
{
//-------------------------------------------------------------
// Simple stored proc
//-------------------------------------------------------------
var parms1 = new testone() { inparm = "abcd" };
var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
var r1 = results1.ToList<TestOneResultSet>();
}
... and I am working on a stored procedure framework (here) which you can call like in one of my test methods shown below...
[TestClass]
public class TenantDataBasedTests : BaseIntegrationTest
{
[TestMethod]
public void GetTenantForName_ReturnsOneRecord()
{
// ARRANGE
const int expectedCount = 1;
const string expectedName = "Me";
// Build the paraemeters object
var parameters = new GetTenantForTenantNameParameters
{
TenantName = expectedName
};
// get an instance of the stored procedure passing the parameters
var procedure = new GetTenantForTenantNameProcedure(parameters);
// Initialise the procedure name and schema from procedure attributes
procedure.InitializeFromAttributes();
// Add some tenants to context so we have something for the procedure to return!
AddTenentsToContext(Context);
// ACT
// Get the results by calling the stored procedure from the context extention method
var results = Context.ExecuteStoredProcedure(procedure);
// ASSERT
Assert.AreEqual(expectedCount, results.Count);
}
}
internal class GetTenantForTenantNameParameters
{
[Name("TenantName")]
[Size(100)]
[ParameterDbType(SqlDbType.VarChar)]
public string TenantName { get; set; }
}
[Schema("app")]
[Name("Tenant_GetForTenantName")]
internal class GetTenantForTenantNameProcedure
: StoredProcedureBase<TenantResultRow, GetTenantForTenantNameParameters>
{
public GetTenantForTenantNameProcedure(
GetTenantForTenantNameParameters parameters)
: base(parameters)
{
}
}
If either of those two approaches are any good?
I have some designer generated code that I am using to query a dataset. The designer generated it because I have a Form with a ReportViewer which created it's own BindingSouce andTableAdapter. I used the "Add Query..." function on the TableAdapter smarttag.
The query is a simple SELECT command. It works find but I'd like to sometimes query for multiple records at once (I am generating a report based on a list of barcodes and there will almost always be many). The designer gave me this code :
public virtual int FillBySampleID(dbReceivedSamplersDataSetAccess.tblReceivedSamplersDataTable dataTable, string Param1) {
//FYI the select command it used is "SELECT * FROM tblReceivedSamplers WHERE SampleID IN (?)"
this.Adapter.SelectCommand = this.CommandCollection[2];
if ((Param1 == null)) {
throw new global::System.ArgumentNullException("Param1");
}
else {
this.Adapter.SelectCommand.Parameters[0].Value = ((string)(Param1));
}
if ((this.ClearBeforeFill == true)) {
dataTable.Clear();
}
int returnValue = this.Adapter.Fill(dataTable);
return returnValue;
}
And that works and is good for a single record so I overloaded this method and created this code to allow me to pass any number of parameters at once using the WHERE...IN SQL statement.
public virtual int FillBySampleID(dbReceivedSamplersDataSetAccess.tblReceivedSamplersDataTable dataTable, string[] Params)
{
//this.Adapter.SelectCommand = this.CommandCollection[2];
if ((Params == null))
{
throw new global::System.ArgumentNullException("Param1");
}
else
{
int numParams = Params.Length;
List<string> lstParamQuesMarks = Enumerable.Repeat("'?'", numParams).ToList();
string strParamQuesMarks = String.Join(",", lstParamQuesMarks);
this.Adapter.SelectCommand.CommandText = "SELECT * FROM tblReceivedSamplers WHERE SampleID IN (" + strParamQuesMarks + ")";
this.Adapter.SelectCommand.Parameters.Clear();
for (int i = 0; i < numParams; i++)
{
this.Adapter.SelectCommand.Parameters.AddWithValue("Param"+i, Params[i]);
}
}
if ((this.ClearBeforeFill == true))
{
dataTable.Clear();
}
int returnValue = this.Adapter.Fill(dataTable);
return returnValue;
}
I thought I was being clever but it doesn't seem to be working. It doesn't give an error or anything. It generated a SelectCommand text of SELECT * FROM tblReceivedSamplers WHERE SampleID IN ('?','?','?','?') if I pass it 4 parameters and all the parameters values look good. When I look at the dataTable while debugging and browse to the count property it is set to 0 (unlike the designer generated code which would be set to 1).
My database is OleDb.
Is what I am trying to do possible?
Parameters should not be enclosed in quotes. use ?, not '?'.