C# - Converting DataReader to DataTable - c#

I want to convert a DataReader to DataTable to display all customers in a list(demoClients) from the Database.
Currently I have this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MySqlConnector;
using ProjectDatabase.Controllers;
using System.Data;
using System.Data.Common;
namespace ProjectDatabase.Models
{
public class demoClientsQuery
{
public AppDb Db { get; }
public demoClientsQuery(AppDb db)
{
Db = db;
}
public async Task<demoClients> FindAllClientsAsync(int id)
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
cmd.Parameters.Add(new MySqlParameter
{
ParameterName = "#id_customer",
DbType = DbType.Int32,
Value = id,
});
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
var result = await ReadAllAsync(dataTable);
return result.Count > 0 ? result[0] : null;
}
public async Task<List<demoClients>> LatestClientsAsync()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
var aa = await LatestClientsAsync2();
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
return await ReadAllAsync(dataTable);
}
public async Task<DataTable> LatestClientsAsync2()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
//return await ReadAllAsync(await cmd.ExecuteReaderAsync());
return await ToDataTable(cmd);
}
private async Task<DataTable> ToDataTable(MySqlCommand cmd)
{
cmd.CommandType = CommandType.Text;
using (DbDataAdapter dataAdapter = new MySqlDataAdapter(cmd))
{
cmd.CommandType = CommandType.Text;
DataTable data = new DataTable();
dataAdapter.Fill(data);
return data;
}
}
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
int id_customer = Convert.ToInt32(dr["id_customer"]);
string delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
string firstname = Convert.ToString(dr["firstname"]);
string lastname = Convert.ToString(dr["lastname"]);
string email = Convert.ToString(dr["email"]);
}
}
return adb_demo_clients;
}
}
}
The only problem I am having is that it doesn't return nothing, it returns empty, something in my code is not returning my data from my database.
Any help would be appreciated.
Thank you for your time.

You have to add item to your adb_demo_clients list, inside the foreach, on the ReadAllAsync method:
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
demoClients d = new demoClients();
d.id_customer = Convert.ToInt32(dr["id_customer"]);
d.delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
// all fields that you need
...
// Add the item in your List
adb_demo_clients.add(d);
}
}
return adb_demo_clients;
}

Related

Not all Variables Bound Oracle REST Web API

I am getting Not all variables Bound.
Here is what My Code Looks Like.
public IEnumerable<VaultService> Get(string Branch_Desg)
{
OracleConnection con = new OracleConnection(constr);
con.Open();
DataTable dt = new DataTable();
//string sql = "select * from wemadummyvaulttable where branch_desg = " + Branch_Desg + "";
string sql = "select BRANCH_ID ,BRANCH_NAME ,BRANCHID_NUMBER ,BRANCH_ACCOUNTNO ,BRANCH_DESG ,CURRENCY ,BRANCH_BALANCE from wemadummyvaulttable where Branch_Desg =" + ":Branch_Desg";
OracleDataAdapter da = new OracleDataAdapter(sql, con);
da.Fill(dt);
List<VaultService> vr = new List<Models.VaultService>(dt.Rows.Count);
if (dt.Rows.Count > 0)
{
foreach (DataRow vaultrecord in dt.Rows)
{
vr.Add(new ReadVaultBal(vaultrecord));
}
}
return vr;
}
For some Reason its not Working like its supposed to.
Screenshot Looks Like this :
Now My Updated Code Looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WEMAVaultREST.Models;
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
using System.Data;
namespace WEMAVaultREST.Controllers
{
public class VaultServiceController : ApiController
{
string constr = "User ID=system; Password=admin1234; Data Source=SAM;";
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// GET api/<controller>/5
/* public VaultService Get(string Branch_Desg)
{
OracleConnection con = new OracleConnection(constr);
con.Open();
DataTable dt = new DataTable();
string sql = "select * from wemadummyvaulttable where branch_desg = '"+Branch_Desg+"'";
OracleDataAdapter da = new OracleDataAdapter(sql,con);
da.Fill(dt);
if (dt.Rows.Count > 0)
{
return new ReadVaultBal(dt.Rows[0]);
}
throw new Exception("Account not found");
}*/
public IEnumerable<VaultService> Get(string Branch_Desg)
{
OracleConnection con = new OracleConnection(constr);
con.Open();
DataTable dt = new DataTable();
//string sql = "select * from wemadummyvaulttable where branch_desg = " + Branch_Desg + "";
string sql = "select BRANCH_ID ,BRANCH_NAME ,BRANCHID_NUMBER ,BRANCH_ACCOUNTNO ,BRANCH_DESG ,CURRENCY ,BRANCH_BALANCE from wemadummyvaulttable where Branch_Desg =" + ":Branch_Desg";
var command = new OracleCommand(sql, con);
command.Parameters.Add("Branch_Desg", Branch_Desg);
OracleDataAdapter da = new OracleDataAdapter(command);
da.Fill(dt);
List<VaultService> vr = new List<Models.VaultService>(dt.Rows.Count);
if (dt.Rows.Count > 0)
{
foreach (DataRow vaultrecord in dt.Rows)
{
vr.Add(new ReadVaultBal(vaultrecord));
}
}
return vr;
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
Thats what i have for now. As you can see it Returns in Sqldeveloper.In the Code, it does not. I do not know what and where to Go from here.
If i run the REST api just like this :
http://localhost:7177/api/VaultService?Branch_Desg=Branch01
It returns this
Try replacing the line
OracleDataAdapter da = new OracleDataAdapter(sql, con);
with
var command = new OracleCommand(sql, con);
command.Parameters.Add("Branch_Desg", branchDesg);
OracleDataAdapter da = new OracleDataAdapter(command);
It seems there isn't a way to add bind parameters directly to an OracleDataAdapter, so we have to create an OracleCommand from the SQL string first, add a value for the parameter :Branch_Desg, and then create the adapter from the command.
Finally, can I please recommend that all Oracle resources (connection, command and data-adapter) are disposed correctly, by creating them in using blocks, e.g.
using (OracleConnection con = new OracleConnection(constr))
{
// code using the database connection 'con'
}

Execute stored procedures in parallel

I have these 2 methods
public DataTable GetData1(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("spGetData1", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id});
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id});
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
and I would like to execute them at once and also get the data for further processing.
I tried something like
var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
but on the last line it crashes with
there is one or more errors.`
The inner exception is
Object reference not set to an instance of an object.
Stack trace
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
The connectionString is obtain from System.Data.Entity.DbContext.Database class
public class DatabaseRepository : IDisposable
{
DbContext dbContext;
public DatabaseRepository()
{
dbContext = new DbContext("connection string ...");
Data = new DataRepository(dbContext.Database);
}
public DataRepository Data { get; set; }
}
but the error is the same even i set connection string manually, so i dont think the error is here.
using (SqlConnection sqlcon = new SqlConnection("connection string ..."))
{
using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
{
...
}
}
How can I do that? I see some examples use Async return type, but I don't want to duplicate these methods.
The database.Connection.ConnectionString is a static string otherwise you cant compile due to a "An object reference is required for the non-static field, method, or property".
With that in mind, its not the Connection String that's un-intstatiated because its static... and even if you purposely initialized the static string to Null then the error message would be:
InnerException = {"The ConnectionString property has not been initialized."}
Here is a repro and the error cannot be produced unless your GetData Methods are in empty objects:
namespace database
{
public class Program
{
static void Main(string[] args)
{
//WORKS!!
var repro = new database.Data();
var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
//FAILS WITH ERROR REPORTED!!
repro = null;
var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
}
}
class Data
{
private string connectionString = "Server=.;Database=CRUD_Sample;Integrated Security=True;Asynchronous Processing = True;";
public DataTable GetData1(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
}
}
Debugging
How do you find the source of a NullReferenceException? Apart from looking at the exception itself - the key is a NRE will be thrown exactly at the location where it occurs then you hover your mouse over the variables on the Line Of Code and see which object is null.
UPDATE:
Task.WaitAll is causing the current thread to block until everything has completed. Use Task.WhenAll so as not to tie up the other threads while waiting for the tasks to complete.
var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));
var taskList = new List<Task> { task1, task2 };
await Task.WhenAll(taskList.ToArray());
var result1 = await task1;
var result2 = await task2;
Original Answer. (Still applicable)
Based on the additional information provided in the comment, I am making assumptions about the class encapsulating the code in question. It is possible that the database.Connection is going out of scope when executed in parallel which may be causing the NRE. Extract the connection string earlier in the life cycle of the object and reuse it when getting the data.
public class MyDataClass {
string connectionString;
private Database database;
public MyDataClass(DbContext context) {
this.database = context.Database;
connectionString = database.Connection.ConnectionString;
}
public DataTable GetData1(int Id) {
var dt = new DataTable();
using (var sqlcon = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand("spGetData1", sqlcon)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (var da = new SqlDataAdapter(cmd)) {
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id) {
var dt = new DataTable();
using (var sqlcon = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand("spGetData2", sqlcon)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (var da = new SqlDataAdapter(cmd)) {
da.Fill(dt);
}
}
}
return dt;
}
}

Refactoring two very similar overloads

I have an overload:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
and another overload:
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
Whose contents are very similar. So similar in fact, the only difference is this line here:
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
I have spent a few hours trying to refactor this guy because they are so similar. I have tried delegates, but it made the code more unreadable. I cannot combine the functionality of the two overloads because the other overloads logic occurs in the middle of the other. Does anyone have any ideas as to how to refactor this into one readable method?
Couple of things you could do:
You could always just use the one method, but with an optional List<StoredProcedureParameters>.
Like this: (I've renamed it to spParams for brevity)
public DataTable ExecuteStoredProcedure(string storedProcedure,
List<StoredProcedureParameters> spParams
= new List<StoredProcedureParameters>())
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
foreach (var parameter in spParams)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
This way, you can call the same method, and choose whether to pass in the List<StoredProcedureParameters> (or not).
Using the parameter with default value of spParams= new List<StoredProcedureParameters>() will mean that any existing code calling the original 1 parameter signature will still work, saving time on extra refactoring.
Plus, it means your existing foreach block will just iterate over an empty list.
OR
You could do similar as the above, just have the spParams value be null as default, and then do a null check, like this:
public DataTable ExecuteStoredProcedure(string storedProcedure,
List<StoredProcedureParameters> spParams = null)
{
//...
if (spParams != null) // Check if the spParams is null
{
foreach(var param in spParams)
{
// Loop in here, if not null
}
}
//...
}
Hope this helps :)
Two approaches -
You can have the first method pass in an empty list of parameters to the second method:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, new List<StoredProcedureParameters>());
}
You can pass null from the first method and add a null check in the second method, like this:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, null);
}
...
if (storedProcedureParameters != null)
{
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType, parameter.LengthOfParameter).Value = parameter.ParameterName;
}
}
You can add a flag to mark if you want to run the foreach or not. Of course there will be necessary to change the parameter name, and this is only one possible variant of refactor:
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters, bool withForEach)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
if(withForEach)
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
Why could you not do something like this...
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, null);
}
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
if(storedProcedureParameters != null)
{
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}

selecting a certain column value from looping in ienumerable

I have a result of IEnumerable from a stored procedure and i am looping through the results inorder to get the value of a column(GUID). I am unsure of how to go about on getting the Guid column from my results set in the foreach loop
this is what i have:
var results = GetGuids(instId);
foreach (var item in results)
{
}
public IEnumerable GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.AddWithValue("#ItemID", id);
return _command.ExecuteReader();
}
}
You can't use most of the normal linq extension methods directly on the non-generic IEnumerable... but you can call .Cast<T>() to make it an IEnumerable<T>. At that point, things get easier:
public IEnumerable<Guid> GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.Add("#ItemID", SqlDbType.Int).Value = id;
return _command.ExecuteReader()
.Cast<DbDataRecord>()
.Select(r => (Guid)r["GuidColumn"]);
}
}
You need to produce the results yourself from the SqlDataReader
var results = GetGuids(instId);
foreach (var item in results)
{
}
public IEnumerable<Guid> GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.AddWithValue("#ItemID", id);
var guids = new List<Guid>();
using (SqlDataReader reader = _command.ExecuteReader())
{
while (reader.Read()
{
guids.Add( (Guid)reader["GuidColumn"]);
}
}
}
}

Loop Through Results from a Method

I have a method that calls a Proc ad returns the data. But I want to call the method and loop through all the results and use the results to plug into an Excel document. I can't seem to figure out how to loop through results coming from the method call. This is my method:
public DataView GetCoupons()
{
string connStr = ConfigurationManager.ConnectionStrings["SiteSqlServer"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand("CPC_GetAllCoupons", conn);
cmd.CommandType = CommandType.StoredProcedure;
//make the dap & ds
SqlDataAdapter dap = new System.Data.SqlClient.SqlDataAdapter(cmd);
DataSet ds = new DataSet();
//open con
if (conn.State == ConnectionState.Closed)
conn.Open();
//get the data
dap.Fill(ds);
//close the conn and return
if (conn.State == ConnectionState.Open)
conn.Close();
return ds.Tables[0].DefaultView;
}
So I want to call that method and get all the results and loop through the results. How would I go about doing that?
Thanks!
When you fill a DataSet, you get a representation of all the result sets returned from the procedure call. To access individual result sets, you simply enumerate over the Tables collection like you are doing:
foreach(DataTable table in ds.Tables)
Then, you can enumerate through all the records in each dataset like so:
foreach(DataRow row in table.Rows)
And then get data from each record:
object something = row["SomeColumnName"];
DataView coupons = GetCoupons();
foreach (var row in coupons) {
// do something here
}
Something like this, should be enough
foreach (DataRowView rowView in dv) //where dv is your DataView
{
DataRow row = rowView.Row;
// Do your stuff here
}
Hope this helps.
I recommend encapsulating the data access in a repository which returns objects of a type which you require. E.g.
public class Coupon
{
public int Foo { get; set; }
public int Bar { get; set; }
}
public class CouponRepository
{
private readonly string connectionString;
public CouponRepository(string connectionString)
{
this.connectionString = connectionString;
}
public IEnumerable<Coupon> GetCoupons()
{
using(var conn = new SqlConnection(this.connectionString))
using(var cmd = new SqlCommand("CPC_GetAllCoupons", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return new Coupon
{
Foo = (int)reader["Foo"],
Bar = (int)reader["Bar"],
};
}
}
}
}
}
Usage:-
var connectionString = ConfigurationManager.ConnectionStrings["SiteSqlServer"].ConnectionString;
var repo = new CouponRepository(connectionString);
foreach (var coupon in repo.GetCoupons())
{
// do something
}
You could use:
Dataview dv = GetCoupons();
foreach(Item item in dv.Table.Rows)
{
//...
}
But there is a lot of ways of doing this.

Categories