How to convert 3 times int in MYSQL datareader? - c#

How can I convert BestellingAantal ToString because I get an error whats says cannot convert type string to bool even I made it a int? It is for a loginsystem with C#
String strSQL;
strSQL =
"SELECT BestellingId, BestellingProductId, BestellingAantal " +
"FROM bestellingen";
oCommand = new MySqlCommand(strSQL, oConnection);
oDataReader = oCommand.ExecuteReader();
//De gegevens staan per record/rij in object DataReader
//Controleer of er data bestaat alvorens verder te gaan
if (oDataReader.HasRows)
{
//lees rij per rij uit DataReader object
//oDataReader.Read krijgt de waarde False,
// indien er geen record meer is
while (oDataReader.Read())
{
int BestellingId = (int)oDataReader["BestellingId"];
int BestellingProductId = (int)oDataReader["BestellingProductId"];
int BestellingAantal = (int)oDataReader["BestellingAantal"];
Bestelling oBestelling = new Bestelling();
oBestelling.BestellingId = BestellingId.ToString();
oBestelling.BestellingProductId = BestellingProductId.ToString();
oBestelling.BestellingAantal = BestellingAantal.ToString();
BestellingLijst.Add(oBestelling);
}
}
oConnection.Close();
if (huidigeGebruiker.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
String Email = String.Parse(huidigeGebruiker.Claims.FirstOrDefault(c => c.Type == ClaimTypes.).Value);
}
return BestellingLijst;
}
}
}

Are you averse to using Dapper? It's a great micro ORM that lets you carry on writing SQL, but do away with all this tedious crap of reading and casting things with datareaders, opening and closing conenctions etc.. you just write an SQL that maps to an object in your c# and get dapper to run the SQL and build a (list of) objects for you.. Like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using MySql.Data.MySqlClient;
using System.Security.Permissions;
public class Program
{
public static void Main()
{
string _connectionString = "Server=127.0.0.1;Database=Bestellingen;Uid=root;Pwd=pwd;";
var service = new BestellingenService(_connectionString);
var result = service.GetBestellingens();
foreach(var item in result)
{
Console.WriteLine(item.BestellingId);
}
}
}
//a class that represents the rows in your db table
public class Bestellingen
{
public int BestellingId { get; set; }
public int BestellingProductId { get; set; }
public int BestellingAantal { get; set; }
}
//a service that runs SQLs and returns you (lists of) your data mapping class
public class BestellingenService
{
private readonly MySqlConnection _conn;
public BestellingenService(string connStr)
{
_conn = new MySqlConnection(connStr);
}
public IEnumerable<Bestellingen> GetBestellingens()
{
var sql = "SELECT BestellingId, BestellingProductId, BestellingAantal FROM bestellingen";
var result = this._conn.Query<Bestellingen>(sql).ToList();
return result;
}
public Bestellingen GetBestellingenById(int bid)
{
var sql = "SELECT BestellingId, BestellingProductId, BestellingAantal FROM bestellingen WHERE id = #pBestellingId";
var result = this._conn.Query<Bestellingen>(sql, new { pBestellingId = bid }).FirstOrDefault();
return result;
}
}

Related

Storing two seperate data fields into an array c# Array Json from Class

Hi I have the following Code whereby in my class I have defined all the classes that I will need to build up my JSON string for an eventual put request. I am trying to put my AttributeColor and AttributeSize Fields into that attributes string[] so that the JSON returns the following :
{"group":null,"productid":"42","sku":"211","money":"20.00","categoryid":"42","attributes":["42","green"]}
myObject.attributes = reader["Sizeattribute"].ToString() + reader["ColorAttribute"].ToString();
where am I going wrong? how can I add two fields to this one array for perfect JSON? Right now I am getting the error Cannot implicitly convert type 'string' to 'string[]'
Code Snippet:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
namespace JSON_Example
{
class Program
{
static void Main(string[] args)
{
// Read From SQL
using (SqlConnection connection = new SqlConnection("Server = localhost;Database=xxxx;UID=admin;PASSWORD=xxxx"))
{
String query = "SELECT * FROM [test].[dbo].[DimProductVariantXXX] "; // Please Change the View that you are pointing towards
using (SqlCommand command = new SqlCommand(query, connection))
{
connection.Open();
List<Product> myObjectList = new List<Product>();
var reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
Product myObject = new Product();
myObject.productid = reader["productid"].ToString();
myObject.sku = reader["sku"].ToString();
myObject.money = reader["money"].ToString();
// myObject.categoryid = Convert.ToString(reader["categoryid"]);
myObject.attributes = reader["Sizeattribute"].ToString() + reader["ColorAttribute"].ToString();
// myObject.variantparentid = reader["variantparentid"].ToString();
myObjectList.Add(myObject);
}
}
Console.WriteLine(myObjectList);
Product product = new Product();
String JSONresult = JsonConvert.SerializeObject(product);
string path = #"D:\json\product.json";
if (File.Exists(path))
{
File.Delete(path);
using (var tw = new StreamWriter(path, true))
{
tw.WriteLine(JSONresult.ToString());
tw.Close();
}
}
else if (!File.Exists(path))
{
using (var tw = new StreamWriter(path, true))
{
tw.WriteLine(JSONresult.ToString());
tw.Close();
}
}
}
}
}
}
class Product
{
public Group group;
public string productid { get; set; }
public string sku { get; set; }
public string money { get; set; }
public string categoryid { get; set; }
public string sizeattribute { get; set; }
public string colorattribute { get; set; }
public List<string>[] attributes { get; set; }
}
}
In the Product class, the attributes property should be defined as a single list or array. It's currently defined as an array of List<string> (not sure if this is a typo).
Update the definition:
public List<string> attributes { get; set; }
Now, myObject.attributes is a List<string> so you need to initialise the list and add the values to it.
You could do this using the Collection Initializer:
myObject.attributes = new List<string>
{
reader["Sizeattribute"].ToString(),
reader["ColorAttribute"].ToString()
};
This should produce the expected JSON.

Sqlite query not returning data in C#

I am using Sqlite with C#. When i run the query in Sqlite browser it returns the correct data but when i run the same query from C# code it does not return data. My C# code is:
foreach (var group in groups)
{
var edgeDataWithGroupCmd = EdgeDatabase._connection.CreateCommand(#"SELECT ad.Id, ad.Name, adType.Id, adType.Name, h.Data, chart.URL
from
tblAdapter ad JOIN
tblAdapterType adType ON ad.AdapterTypeId = adType.Id JOIN
tblHealth h ON ad.HealthId = h.Id JOIN
tblCharts chart ON ad.ChartId = chart.Id
WHERE ad.GroupId = " + group.Id);
List<EdgeData> edgeDatas = edgeDataWithGroupCmd.ExecuteQuery<EdgeData>();
}
When i run this query in C#, query does return a list of "EdgeData" but all the values are set to there default and not the exact data from database. I am using sqlite-net.
EdgeData is a custom class:
public class EdgeData
{
public int AdapterId { get; set; }
public string AdapterName { get; set; }
public int AdapterTypeId { get; set; }
public string AdapterTypeName { get; set; }
public bool IsConnected { get; set; }
public int MaxRefreshRate { get; set; }
public int AchievedRefreshRate { get; set; }
public string ChartLink { get; set; }
}
//get one instance, and use function in logic/business layer to create list
//or rebuild function to return a list<EdgeData>
public EdgeData GetEdgeData(int groupID)
{
connection.open();
EdgeData edgeData;
StringBuilder sb = new StringBuilder();
sb.Append("SELECT ad.Id, ad.Name, adType.Id, adType.Name, h.Data, chart.URL
from
tblAdapter ad JOIN
tblAdapterType adType ON ad.AdapterTypeId = adType.Id JOIN
tblHealth h ON ad.HealthId = h.Id JOIN
tblCharts chart ON ad.ChartId = chart.Id
WHERE ad.GroupId = #GroupId");
SqlDataReader data;
String sql = sb.ToString();
//Use sql injection instead of "+"
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
cmd.Parameters.AddWithValue("#GroupId", groupID);
data = cmd.ExecuteReader();
}
while (data.Read())
{
int AdapterId = (int)data["ad.Id"]
string AdapterName = (string)data["ad.Name"]
int AdapterTypeId = ...
string AdapterTypeName = ...
bool IsConnected = ...
int MaxRefreshRate = ...
int AchievedRefreshRate = ..
string ChartLink = ..
edgeData = new EdgeData(AdapterId, AdapterName, etc...);
}
connection.Close();
return edgeData;
}

Web API using the ODATA Query

We created a Web API which takes a Array of ID as input parameters and queries the Oracle Database, returns the result in JSON format. If the data returned is very large it throws an OutOfMemoryException issue. So planning to add an additional query parameter, which set of columns to return data.
Instead of loading loading the entire result set in to memory currently we are before serializing it out to the HttpResponseMessage. Our query is complex and huge, which selects 45 columns.I am just giving only few of the columns here as an example in the below code,
using PSData.Models;
namespace PSData.Controllers
public class PDataController : ApiController
{
public HttpResponseMessage Getdetails([FromUri] string[] id)
{
List<OracleParameter> prms = new List<OracleParameter>();
string connStr = ConfigurationManager.ConnectionStrings["PDataConnection"].ConnectionString;
using (OracleConnection dbconn = new OracleConnection(connStr))
{
DataSet userDataset = new DataSet();
var strQuery = #"SELECT STCD_PRIO_CATEGORY_DESCR.DESCR AS CATEGORY,
STCD_PRIO_CATEGORY_DESCR.SESSION_NUM AS SESSION_NUMBER,
Trunc(STCD_PRIO_CATEGORY_DESCR.START_DATE) AS SESSION_START_DATE,
STCD_PRIO_CATEGORY_DESCR.START_DATE AS SESSION_START_TIME ,
Trunc(STCD_PRIO_CATEGORY_DESCR.END_DATE) AS SESSION_END_DATE,
STCD_PRIO_CATEGORY_DESCR.END_DATE AS SESSION_END_TIME,
Round((TO_DATE (TO_CHAR (STCD_PRIO_CATEGORY_DESCR.END_DATE, 'dd/mm/yyyy hh24:mi'),'dd/mm/yyyy hh24:mi') - TO_DATE (TO_CHAR (STCD_PRIO_CATEGORY_DESCR.START_DATE, 'dd/mm/yyyy hh24:mi'),'dd/mm/yyyy hh24:mi'))*1440)AS SESSION_DURATION_MINUTES
from STCD_PRIO_CATEGORY
where STCD_PRIO_CATEGORY_DESCR.STD_REF IN(";
var sb = new StringBuilder(strQuery);
for (int x = 0; x < inconditions.Length; x++)
{
sb.Append(":p" + x + ",");
var p = new OracleParameter(":p" + x, OracleDbType.NVarchar2);
p.Value = inconditions[x];
prms.Add(p);
}
if (sb.Length > 0) sb.Length--;
strQuery = sb.Append(")").ToString();
var returnObject = new { data = new OracleDataTableJsonResponse(connStr, strQuery, prms.ToArray()) };
var response = Request.CreateResponse(HttpStatusCode.OK, returnObject, MediaTypeHeaderValue.Parse("application/json"));
ContentDispositionHeaderValue contentDisposition = null;
if (ContentDispositionHeaderValue.TryParse("inline; filename=PSData.json", out contentDisposition))
{
response.Content.Headers.ContentDisposition = contentDisposition;
}
return response;
}
Now the api call is like
http://localhost:430/api/PData?id=JW21&id=BL02&id=AB02
I understand that there are these keywords top and skip in ODATA using IQueryable so the API call is
http://localhost:9658/api/values?$top=2&$skip=2
I am not sure how to implement the above in my approach, I created the Model class like below
namespace PSData.Models
{
public class StudyDataModel
{
[Key]
public string CATEGORY { get; set; }
public int SESSION_NUMBER { get; set; }
public DateTime SESSION_START_DATE { get; set; }
public DateTime SESSION_START_TIME { get; set; }
public Nullable<DateTime> SESSION_END_DATE { get; set; }
public Nullable<DateTime> SESSION_END_TIME { get; set; }
public string DOSE_ACTIVITY { get; set; }
public Nullable<decimal> SESSION_DURATION_MINUTES { get; set; }
}
}
And
namespace PSData.Models
{
public class StudyDataContext:DbContext
{
public DbSet<StudyDataModel> details { get; set; }
}
}
But I am not sure how to implement them in my actual controller. Any help with this greatly appreciated.

C# Dapper problems with IN statement using string array

I have an string array (query.Tags) for filter a list of values and each time, procces just take the first value of string array during the query execution.
I tried several combinations but nothing changed. Of course, I tested all of these SQL statements in SQL SERVER View.
Can you tell me what I doing wrong?
public IEnumerable<ActorDto> SearchMembersInLists(ListMembersQuery query)
{
IEnumerable<ActorDto> result = null;
var sql = #"select DISTINCT t.ActorId,
a.Id, a.TypeId, a.Name, a.Identifier
FROM [ActorTag] t
INNER JOIN [Actor] a ON t.ActorId = a.Id
where t.Name IN #tags
";
using (var cnx = DbConnectionFactory.GetDefault().GetConnection())
{
cnx.Open();
var query_result = cnx.QueryMultiple(sql, new { query.Tags});
result = query_result.Read<ActorDto>();
}
return result;
}
the original code is this, i just tried to simplify as I could
public IEnumerable<ActorDto> SearchMembersInLists(ListMembersQuery query)
{
IEnumerable<ActorDto> result = null;
var sql = #"
SELECT DISTINCT a.Id, a.TypeId, a.Name, a.Identifier,a.Description, a.Email, a.PictureUrl, a.DisplayName --Actor
FROM [RoleMember] lm
INNER JOIN [Actor] a ON lm.ActorId = a.Id
WHERE {tag_filter} {lists_filter}
ORDER BY a.DisplayName DESC OFFSET #pageIndex ROWS FETCH NEXT #pageSize ROWS ONLY
";
bool has_tags = true;
bool has_lists = true;
if (query.Tags != null && query.Tags.Any())
{
sql = sql.Replace("{tag_filter}", "a.Id IN (SELECT t.ActorId FROM [ActorTag] t WHERE t.Name IN #tags)");
has_tags = true;
}
else
{
sql = sql.Replace("{tag_filter}", "");
has_tags = false;
}
if (query.Lists != null && query.Lists.Any())
{
if (has_tags)
{
sql = sql.Replace("{lists_filter}", "AND lm.RoleId IN #lists");
}
else
{
sql = sql.Replace("{lists_filter}", "lm.RoleId IN #lists");
}
has_lists = true;
}
else
{
sql = sql.Replace("{lists_filter}", "");
has_lists = false;
}
if (!has_tags && !has_lists){
sql = sql.Replace("WHERE", "");
}
var values = new
{
lists = query.Lists,
tags = query.Tags,
pageIndex = query.PageIndex * query.PageSizeOrDefault,
pageSize = query.PageSizeOrDefault
};
using (var cnx = DbConnectionFactory.GetDefault().GetConnection())
{
cnx.Open();
result = cnx.Query<ActorDto>(sql, values);
}
return result;
}
There is nothing wrong in the code shown, assuming you're using the latest version of dapper. A similar example is shown below (that can be run in a console exe etc). Please check your data is what you expect.
Note; the query code can actually be significantly simplified, but I wanted to keep it as similar to your example as possible. The simple alternative is here:
public static IEnumerable<ActorDto> SearchMembersInLists(ListMembersQuery query)
{
using (var cnx = GetConnection())
{
return cnx.Query<ActorDto>(
#"select Id, Name from FooActors where Name IN #Tags", new { query.Tags });
}
}
The full program with the more complex query layout is shown below. The output is:
2: Barney
4: Betty
using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main()
{
// reset and populate
using (var conn = GetConnection())
{
conn.Open();
try { conn.Execute(#"drop table FooActors;"); } catch { }
conn.Execute(#"create table FooActors (
Id int not null primary key identity(1,1),
Name nvarchar(50) not null);");
conn.Execute(#"insert FooActors(Name) values(#Name);", new[]
{
new { Name = "Fred" },
new { Name = "Barney" },
new { Name = "Wilma" },
new { Name = "Betty" },
});
}
// run a demo query
var tags = new[] { "Barney", "Betty" };
var query = new ListMembersQuery { Tags = tags };
var actors = SearchMembersInLists(query);
foreach(var actor in actors)
{
Console.WriteLine("{0}: {1}", actor.Id, actor.Name);
}
}
public static IDbConnection GetConnection()
{
return new SqlConnection(
#"Initial Catalog=master;Data Source=.;Integrated Security=SSPI;");
}
public class ActorDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ListMembersQuery
{
public string[] Tags { get; set; }
}
public static IEnumerable<ActorDto> SearchMembersInLists(ListMembersQuery query)
{
IEnumerable<ActorDto> result = null;
const string sql = #"select Id, Name from FooActors where Name IN #Tags";
using (var cnx = GetConnection())
{
cnx.Open();
var query_result = cnx.QueryMultiple(sql, new { query.Tags });
result = query_result.Read<ActorDto>();
}
return result;
}
}

How to Deserialize datareader or data table to c# class

i have populated data reader from db table and i have class like
public class CandidateApplication
{
public string EmailID { get; set; }
public string Name { get; set; }
public string PhoneNo { get; set; }
public string CurrentLocation { get; set; }
public string PreferredWorkLocation { get; set; }
public int RoleApplingFor { get; set; }
public string CurrentJobTitle { get; set; }
public int EducationLevel { get; set; }
public decimal SalaryExpected { get; set; }
public string AvailableTime { get; set; }
public int AdvertID { get; set; }
public bool SignForAlert { get; set; }
public string CVInText { get; set; }
public string CVFileName { get; set; }
public bool IsDownloaded { get; set; }
public string specialization { get; set; }
public bool isallocated { get; set; }
public int id { get; set; }
public string AdvertAdditionalInfo { get; set; }
}
i can populate the above class in loop. we can iterate in data reader and populate class but i want to know is there any short cut way to populate class from data reader.
if data deserialization is possible from data reader to class then also tell me if few fields are there in class which are not there in data reader then how to handle the situation.
You don't need to use a Data Reader, You could just Populate the Data into a DataTable, and use the below method to create a List of your CandidateApplication Class.
The Call :-
List<CandidateApplication> CandidateList = GetCandidateInformation();
The Method that generates the list :-
public List<CandidateApplication> GetCandidateInformation()
{
DataTable dt = new DataTable();
using (OleDbConnection con = new OleDbConnection(ConfigurationManager.AppSettings["con"]))
{
using (OleDbCommand cmd = new OleDbCommand("SELECT * FROM [TableName]", con))
{
var adapter = new OleDbDataAdapter();
adapter.SelectCommand = cmd;
con.Open();
adapter.Fill(dt);
var CandApp = (from row in dt.AsEnumerable()
select new CandidateApplication
{
EmailID = row.Field<string>("EmailID"),
Name = row.Field<string>("Name"),
PhoneNo = row.Field<string>("PhoneNo"),
CurrentLocation = row.Field<string>("CurrentLocation"),
PreferredWorkLocation = row.Field<string>("PreferredWorkLocation"),
RoleApplingFor = row.Field<int>("RoleApplingFor"),
CurrentJobTitle = row.Field<string>("CurrentJobTitle"),
EducationLevel = row.Field<int>("EducationLevel "),
SalaryExpected = row.Field<decimal>("SalaryExpected"),
AvailableTime = row.Field<string>("AvailableTime"),
AdvertID = row.Field<int>("AdvertID"),
SignForAlert = row.Field<bool>("SignForAlert"),
CVInText = row.Field<string>("CVInText"),
CVFileName = row.Field<string>("CVFileName"),
IsDownloaded = row.Field<bool>("IsDownloaded"),
Specialization = row.Field<string>("Specialization"),
Isallocated = row.Field<bool>("Isallocated"),
Id = row.Field<int>("Id"),
AdvertAdditionalInfo = row.Field<string>("AdvertAdditionalInfo")
}).ToList();
return CandApp;
}
}
}
Although not an answer to your question, I would suggest you to consider the following workaround, which uses a SqlDataAdapter instead of a data reader:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Xml.Serialization;
class Program
{
static void Main(string[] args)
{
var cs = "YourConnectionString";
var xml = "";
using (var con = new SqlConnection(cs))
using (var c = new SqlCommand("SELECT * FROM CandidateApplication", con))
{
con.Open();
using (var adapter = new SqlDataAdapter(c))
{
var ds = new DataSet("CandidateApplications");
ds.Tables.Add("CandidateApplication");
adapter.Fill(ds, ds.Tables[0].TableName);
xml = ds.GetXml();
}
}
// We need to specify the root element
var rootAttribute = new XmlRootAttribute();
// The class to use as the XML root element (should match the name of
// the DataTable in the DataSet above)
rootAttribute.ElementName = "CandidateApplications";
// Initializes a new instance of the XmlSerializer class that can
// serialize objects of the specified type into XML documents, and
// deserialize an XML document into object of the specified type.
// It also specifies the class to use as the XML root element.
// I chose List<CandidateApplication> as the type because I find it
// easier to work with (but CandidateApplication[] will also work)
var xs = new XmlSerializer(typeof(List<CandidateApplication>), rootAttribute);
// Deserialize the XML document contained by the specified TextReader,
// in our case, a StringReader instance constructed with xml as a parameter.
List<CandidateApplication> results = xs.Deserialize(new StringReader(xml));
}
}
For those properties that are missing in the retrieved data, you could declare a private field with a default value:
string _advertAdditionalInfo = "default";
public string AdvertAdditionalInfo
{
get
{
return _advertAdditionalInfo;
}
set
{
_advertAdditionalInfo = value;
}
}
If you would like to enforce that the retrieved data will not fill in a specific property, use:
[XmlIgnoreAttribute]
public string AdvertAdditionalInfo { get; set; }
I made a generic function for converting the SELECT result from an OleDbCommand to a list of classes.
Let's say that I have a class that looks like this, which maps to the columns in the database:
internal class EconEstate
{
[Column(Name = "basemasterdata_id")]
public Guid BaseMasterDataId { get; set; }
[Column(Name = "basemasterdata_realestate")]
public Guid? BaseMasterDataRealEstate { get; set; }
[Column(Name = "business_area")]
public string BusinessArea { get; set; }
[Column(Name = "profit_centre")]
public int ProfitCentre { get; set; }
[Column(Name = "rentable_area")]
public decimal RentableArea { get; set; }
}
Then I can get a list of those EconEstate objects using this code:
public void Main()
{
var connectionString = "my connection string";
var objects = ReadObjects<EconEstate>(connectionString, "EMBLA.EconEstates").ToList();
}
private static IEnumerable<T> ReadObjects<T>(string connectionString, string tableName) where T : new()
{
using (var connection = new OleDbConnection(connectionString))
{
connection.Open();
using (var command = new OleDbCommand($"SELECT * FROM {tableName};", connection))
{
var adapter = new OleDbDataAdapter
{
SelectCommand = command
};
var dataTable = new DataTable();
adapter.Fill(dataTable);
foreach (DataRow row in dataTable.Rows)
{
var obj = new T();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var columnAttribute = propertyInfo.GetCustomAttributes().OfType<ColumnAttribute>().First();
var value = row[columnAttribute.Name];
var convertedValue = ConvertValue(value, propertyInfo.PropertyType);
propertyInfo.SetValue(obj, convertedValue);
}
yield return obj;
}
}
}
}
private static object ConvertValue(object value, Type targetType)
{
if (value == null || value.GetType() == typeof(DBNull))
{
return null;
}
if (value.GetType() == targetType)
{
return value;
}
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (value is string stringValue)
{
if (underlyingTargetType == typeof(int))
{
return int.Parse(stringValue);
}
else if (underlyingTargetType == typeof(decimal))
{
return decimal.Parse(stringValue);
}
}
var valueType = value.GetType();
var constructor = underlyingTargetType.GetConstructor(new[] { valueType });
var instance = constructor.Invoke(new object[] { value });
return instance;
}
As you can see, the code is generic, making it easy to handle different tables and classes.

Categories