I'm trying to create a generic data retrieval process. What I have currently works, but there is a part of it that doesn't seem right and I'm hoping there is a better way to accomplish it.
So the idea is that I have classes for each table in the database, here is an example of a class:
public class CMCGRGRGROUP : IFacetsObject<CMCGRGRGROUP>
{
public int GRGR_CK { get; set; }
public string GRGR_NAME { get; set; }
public string GRGR_ADDR1 { get; set; }
public IEnumerable<CMCGRGRGROUP> ToObject(DataTable table)
{
return table.AsEnumerable().Select(row =>
{
return new CMCGRGRGROUP
{
GRGR_CK = Convert.ToInt32(row["GRGR_CK"]),
GRGR_NAME = row["GRGR_NAME"].ToString(),
GRGR_ADDR1 = row["GRGR_ADDR1"].ToString()
};
});
}
}
You'll notice that the class implements an interface of its own type. The interface simply defines a method called ToObject, which is used to convert a datatable to a class of that particular type:
public interface IFacetsObject<T>
{
IEnumerable<T> ToObject(DataTable obj);
}
Now, here is the method that I am using to execute a query:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj) where T : new()
{
using (var conn = new AseConnection(_conn))
{
conn.Open();
var cmd = new AseCommand(sql, conn);
var dt = new DataTable();
var da = new AseDataAdapter(sql, conn);
da.Fill(dt);
return obj.ToObject(dt); //this is the interface method
}
}
So the main question is:
How can the generic method know that T should implement IFacetsObject<T>? That way I don't have to pass IFacetsObject<T> as a parameter. Ideally, I could change the return line to be something like this:
return T.ToObject(dt);
And call it like this:
var result = ExecuteQuery<CMCGRGRGROUP>(sql).Take(5);
Instead of like this:
var result = ExecuteQuery<CMCGRGRGROUP>(sql, new CMCGRGRGROUP()).Take(5);
I'll admit that I'm not terribly familiar with generics yet so there may be something within the implementation that isn't right.
You can add a constraint on your ExecuteQuery method. You already have one: requiring that T be newable. You'd declare it like:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj)
where T : IFacetsObject<T>, new()
{
using (var conn = new AseConnection(_conn))
{
conn.Open();
var cmd = new AseCommand(sql, conn);
var dt = new DataTable();
var da = new AseDataAdapter(sql, conn);
da.Fill(dt);
return obj.ToObject(dt); //this is the interface method
}
}
So it now knows T is an IFacetsObject<T>. You could now do:
public IEnumerable<T> ExecuteQuery<T>(string sql)
where T : IFacetsObject<T>, new()
{
using (var conn = new AseConnection(_conn))
{
conn.Open();
var cmd = new AseCommand(sql, conn);
var dt = new DataTable();
var da = new AseDataAdapter(sql, conn);
da.Fill(dt);
return new T().ToObject(dt); //this is the interface method
}
}
Which IMO is still pretty ugly.
EDIT Response:
Note that you cannot call T.ToObject - an interface cannot define a static method. The workaround is the use of new to create a new instance of T and call the instance method.
You need a generic constraint on your interface. Declare it like this:
public interface IFacetsObject<T> where T : IFacetsObject<T>
{
IEnumerable<T> ToObject(DataTable obj);
}
In order to get this to work, you also have to change your declaration like this:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj)
where T : IFacetsObject<T>, new()
Related
I am trying to write a generic method to get the data from tables.
I am using sqlite-net ORM.
My methods compile well for delete:
public bool DeleteItem<T>(T NewItem)
{
SQLiteConnection conn = new SQLiteConnection(GetConnection());
var result = conn.Delete<T>(NewItem);
//...
}
And
public void CreateTable<T>()
{
SQLiteConnection conn = new SQLiteConnection(GetConnection());
conn.CreateTable<T>();
conn.Close();
}
But if I try to get Table data as list, I get a compile error at conn.Table:
T Must be a non-Abstract Type ....
Here is my code that does not want to compile:
public List<T> GetAllItems<T>(string SelTable)
{
SQLiteConnection conn = new SQLiteConnection(GetConnection());
List<T> MyItems = conn.Table<T>().ToList();
conn.Close();
return MyItems;
}
The definition of Table in SQLiteConnection is
public TableQuery<T> Table<T>() where T : new();
So you have to add type constraints:
public bool DeleteItem<T>(T NewItem) where T : new() // not required, but useful
{
SQLiteConnection conn = new SQLiteConnection("xx");
var result = conn.Delete<T>(NewItem);
return true;
}
public void CreateTable<T>() where T : new() // not required, but useful
{
SQLiteConnection conn = new SQLiteConnection("xx");
conn.CreateTable<T>();
conn.Close();
}
public List<T> GetAllItems<T>(string SelTable) where T : new()
{
SQLiteConnection conn = new SQLiteConnection("xx");
List<T> MyItems = conn.Table<T>().ToList();
conn.Close();
return MyItems;
}
I have a common method which is being called multiple times(around 30-35 references in the project). This method is basically fetching data from DB into data table.
Following is the testable code:
public class MyApp
{
private readonly IDataProvider _dbProvider;
public MyApp(IDataProvider dbProvider)
{
_dbProvider = dbProvider;
}
public void Process()
{
string query = "something";
Helper h = new Helper(_dbProvider);
// This method will be called in the Process method several times
var data = h.GetData(query);
}
}
public interface IDataProvider
{
IDbConnection CreateConnection(string connectionString);
DataTable FillDatatableFromAdapter(IDbCommand command);
}
public class DataProvider : IDataProvider
{
public IDbConnection CreateConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
public DataTable FillDatatableFromAdapter(IDbCommand command)
{
DataSet dataSet = new DataSet();
SqlCommand sqlCommand = command as SqlCommand;
using (SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand))
{
sqlDataAdapter.Fill(dataSet);
}
if (dataSet.Tables.Count == 0)
return null;
return dataSet.Tables[0];
}
}
public class Helper
{
private readonly IDataProvider _dbProvider;
public Helper(IDataProvider dbProvider)
{
_dbProvider = dbProvider;
}
public DataTable GetData(string query)
{
DataTable table = new DataTable();
using (IDbConnection connection =
_databaseProvider.CreateConnection(_connectionString))
{
connection.Open();
using (IDbCommand command = connection.CreateCommand())
{
command.CommandText = query;
command.Connection = connection;
table = _dbProvider.FillDatatableFromAdapter(command);
}
}
return table;
}
}
I have mocked DB classes to not hit DB from unit tests.
Following is the test case code:
[TestMethod]
public void TestMyApp()
{
Mock<IDbCommand> mockDbCommand = new Mock<IDbCommand>();
Mock<IDbConnection> mockDbConnection = new Mock<IDbConnection>();
Mock<IDataProvider> mockDatabaseProvider = new Mock<IDataProvider();
mockDbConnection.Setup(m => m.CreateCommand()).Returns(mockDbCommand.Object);
mockDatabaseProvider.Setup(m => m.CreateConnection(It.IsAny<string>())).Returns(mockDbConnection.Object);
DataTable table = new DataTable();
mockDatabaseProvider.SetupSequence(mock => mock.FillDatatableFromAdapter(It.IsAny<IDbCommand>()))
.Returns(dataTable);
MyApp app = new MyApp(mockDatabaseProvider.Object);
app.Process();
// And then after that I am testing some data.
}
I will be calling GetData() method several times and therefore FillDatatableFromAdapter will also be called several times. As in the above test case I have mocked FillDatatableFromAdapter method and I am returning some fake data table for further testing.
I am aware of SetupSequence method in Moq which I can use to return multiple data tables from the mocked method everytime that method is called.
I want a suggestion is that approach correct because then I will have to create that many number of Data tables which will be return from the mocked method using SetupSequence? Or is there some other better approach?
Any help??
You need to define what you want to test.
A small refactoring will help you better test your code
public class MyApp
{
private readonly IDataProvider _dbProvider;
private readonly IHelper _h;
public MyApp(IDataProvider dbProvider)
{
_dbProvider = dbProvider;
_h = new Helper(_dbProvider);
}
public void Process()
{
string query = "something";
// This method will be called in the Process method several times
var data = _h.GetData(query);
}
}
public interface IDataProvider
{
IDbConnection CreateConnection(string connectionString);
DataTable FillDatatableFromAdapter(IDbCommand command);
}
public class DataProvider : IDataProvider
{
public IDbConnection CreateConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
public DataTable FillDatatableFromAdapter(IDbCommand command)
{
DataSet dataSet = new DataSet();
SqlCommand sqlCommand = command as SqlCommand;
using (SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand))
{
sqlDataAdapter.Fill(dataSet);
}
if (dataSet.Tables.Count == 0)
return null;
return dataSet.Tables[0];
}
}
public interface IHelper
{
public DataTable GetData(string query);
}
public class Helper
{
private readonly IDataProvider _dbProvider;
public Helper(IDataProvider dbProvider)
{
_dbProvider = dbProvider;
}
public DataTable GetData(string query)
{
DataTable table = new DataTable();
using (IDbConnection connection =
_databaseProvider.CreateConnection(_connectionString))
{
connection.Open();
using (IDbCommand command = connection.CreateCommand())
{
command.CommandText = query;
command.Connection = connection;
table = _dbProvider.FillDatatableFromAdapter(command);
}
}
return table;
}
}
Now you can have 4 isolated tests
[TestMethod]
public void TestProccessFunctionCallHelperGetData()
{
Mock<IHelper> mockHelper = new Mock<IHelper>();
Mock<IDataProvider> mockDatabaseProvider = new Mock<IDataProvider();
mockHelper.Setup(m => m.getData(It.IsAny<string>())).Returns(new DataTable());
MyApp app = new MyApp(mockDatabaseProvider.Object, mockHelper.Setup);
app.Process();
Assert.AreEquals(mockHelper.numTimesCalled, 1);
}
[TestMethod]
public void TestHelperGetData()
{
var query = ""; //TODO: write dummy query
var dataTableToReturn = new DataTable(); //TODO: add some data
Mock<IDataProvider> mockDatabaseProvider = new Mock<IDataProvider();
mockDatabaseProvider.Setup(m => m.CreateConnection(It.IsAny<string>())).Returns(new SqlConnection("dummy connection string"));
mockDatabaseProvider.Setup(m => m.FillDatatableFromAdapter(It.IsAny<IDbCommand>())).Returns(dataTableToReturn );
IHelper h = new Helper(mockDatabaseProvider.Object);
var actualDataTable = h.getData(query);
Assert.AreEqual(dataTableToReturn, actualDataTable );
}
[TestMethod]
public void TestDataProviderFillDataTableFromAdapter()
{
//This test seems to me more like integration test because you need to mock a db or use a real db
}
[TestMethod]
public void TestYourData()
{
// Create a dataTable with data so you can continue with your testing
}
I have the following interface and class
public interface IOwner
{
int Owner_pkid { get; set; }
string Name { get; set; }
}
public class Owner : IOwner
{
public Owner()
{
}
public int Owner_pkid { get; set; }
public string Name { get; set; }
}
I then have the following Data Access Methods in a separate class
public List<IOwner> GetAllOwners()
{
var sql = "SELECT owner_pkid, name from dbo.Owners ";
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.CommandType = CommandType.Text;
List<IOwner> owners = new List<IOwner>();
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
BindResultSet<IOwner>(reader, owners);
return owners;
}
}
private void BindResultSet<T>(SqlDataReader reader, List<T> items) where T : new()
{
int counter = 0;
if (!reader.IsClosed)
{
while (reader.Read())
{
T record = GetNextDataObject<T>(items, counter);
counter++;
BindRecord<T>(reader, record);
}
}
}
private T GetNextDataObject<T>(List<T> items, int pointer) where T : new()
{
if (pointer < items.Count)
{
return items[pointer];
}
else
{
items.Add(new T());
return items[items.Count - 1];
}
}
private void BindRecord<IOwner>(SqlDataReader reader, IOwner owner)
{
owner.Name = (string)reader["name"];
owner.Owner_pkid = (int)reader["owner_pkid"];
}
I am getting 2 separate errors with this code:
In the GetAllOwners method I am getting an error with the call to BindResultSet which says
IOwner must be a non-abstract type with a parameterless constructor in order to be used here
I have a parameterless constrctor on the implementing class - don't think i can add one to the interface
In the final BindRecord method I am getting an error whereby the two property names are not recognized. This is possibly as a result of the first issue
The problem is that you cannot do new on an interface. So "new T" where T is IOwner will not work.
There are a number of different ways to fix this. The cleanest approach would be to add a factory interface whose only purpose is to create IOwner objects, say IOwnerFactory. Instantiate the factory at the top level and pass it down to the methods that need to create objects.
So instead of item.Add(new T()) use item.Add(ownerFactory.Create())
I have a design problem with a 3 Tier application. Usually my database layer is only 1 class like this one:
public class DA
{
string _connString = "";
public DA()
{
_connString = ConfigurationManager.ConnectionStrings["MyConnString"].ToString();
}
public DA(string connString)
{
_connString = connString;
}
private SqlConnection GetConnection()
{
...
}
public Employee GetEmployee(int idEmployee)
{
...
}
public Whatever GetWhatever(int idWhatever)
{
...
}
...
But now I have a pretty big project and I would like to separate the DA class into smaller classes like DA_Employee, DA_Whatever, etc.
I would like to instantiate only DA one time and access the other classes like that:
DA db = new DA(connString);
db.Employee.GetEmployee(12);
db.Whatever.GetWhatever(89);
db.Whatever.UpdateWhatever(89, "newname");
I would prefer NOT having something like this:
DA db = new DA(connString);
DA_Employee dbEmployee = new DA_Employee(connString);
DA_Whatever dbWhataver = new DA_Whatever(connString);
I think I can instantiate all my classes in my main constructor and have some properties to access them?
How can I give access to all classes to GetConnection()?
Any help and reference appreciated.
Thanks!
Yes, you make the classes a property of DA.
public interface IRepository<T>
{
T GetById(int id);
}
public class EmployeeRepository : IRepository<Employee>
{
private SqlConnection sqlConn;
public EmployeeRepository(SqlConnection sqlconn)
{
this.sqlConn = sqlConn;
}
public Employee GetById(int id)
{
return new Employee();
}
}
Pass the SqlConnection as a constructor dependency.
public class DA : IDisposable
{
private SqlConnection sqlConn;
private IRepository<Employee> employeeRepo;
private IReposiotry<Whatever> whateverRepo;
public DA(string connectionString)
{
this.sqlConnection = GetSqlConnection(connectionString);
this.employeeRepo = new EmployeeRepository(this.sqlConnection);
this.whateverRepo = new WhateverRepository(this.sqlConnection);
}
public IRepository<Employee> Employee { get { return employeeRepo; } }
public IRepository<Whatever> Whatever { get { return whateverRepo; } }
}
And its usage
using (var db = new DA("connectionString"))
{
db.Employee.GetById(1);
db.Whatever.GetById(10);
}
First Look at my code
namespace HealthClub
{
public partial class frmTrainerMaster : Form
{
DataTable dt = new DataTable();
frmHome Home = new frmHome();
public frmTrainerMaster()
{
InitializeComponent();
}
}
private void frmTrainerMaster_Load(object sender, EventArgs e)
{
FillValues("UserNameText");
}
public void FillValues(string UserName)
{
DataTable DT;
SqlCommand cmd = new SqlCommand();
try
{
cmd.Connection = Connections.Connection[UserName];
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "TrainerMaster_pro";
cmd.Parameters.AddWithValue("Option", "FillValues".Trim());
if (Connections.Connection[UserName].State == ConnectionState.Closed)
Connections.Connection[UserName].Open();
SqlDataAdapter adp = new SqlDataAdapter(cmd);
DT = new DataTable();
adp.Fill(DT);
lblId___.Text = DT.Rows[0][0].ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
cmd.Parameters.Clear();
cmd.Dispose();
Connections.Connection[UserName].Close();
}
}
}
Now I am calling FillValues() from another class like this
class AnotherClass
{
public void TestMethod(string FormName)
{
Type tp = Type.GetType("HealthClub." + FormName);
object myobj = Activator.CreateInstance(tp);
MethodInfo method = myobj.GetType().GetMethod("FillValues");
object[] parametersArray = new object[] { UserName };
method.Invoke(myobj, parametersArray);
}
}
If you look at the FillValues(), I am assigning the database value to a label. When I am calling it in my first class in page load it's working fine.
But when I am Invoking the medthod from second class, Method invokes but database value does not assign to the label.
What extra effort I need to do ?
There is a class and there is an instance. This is very basic concept you need to understand (not just in C#, but in any objective-oriented language).
// class example
class FrmTrainerMaster { public int SomeProperty { get; set;} }
When you create new instance:
// creates one window
var frmTrainerMasterInstanceOne = new FrmTrainerMaster();
frmTrainerMasterInstanceOne.SomeProperty = 1;
// creates second window
var frmTrainerMasterInstanceTwo = new FrmTrainerMaster();
frmTrainerMasterInstanceTwo.SomeProperty = 2;
Instances are SEPARATE - so at this point querying
// will return 1
Console.Out.WriteLine(frmTrainerMasterInstanceOne.SomeProperty);
// will return 2
Console.Out.WriteLine(frmTrainerMasterInstanceTwo.SomeProperty);
With reflection var myobj = Type.GetType("HealthClub.FrmTrainerMaster"); is equal to var myobj = new FrmTrainerMaster(); so by doing anything with myobj, you still can't affect frmTrainerMasterInstanceOne or frmTrainerMasterInstanceTwo.
What do you need is actually method how to pass reference to instance (of FrmTrainerMaster class) to place where you need it (lets call it AnotherForm), there is no magic list of all instances for given class unless you explicitly build it.
public partial class FrmTrainerMaster : Form
{
public void FillValues(string userName) { ... }
}
One way is via constructor injection (generally proffered - since at time when object (AnotherForm) is constructed you have it in valid state (i.e. with all dependencies initialized).
public class AnotherForm : Form {
private readonly FrmTrainMaster _frmTrainMaster;
public AnotherForm(FrmTrainMaster frmTrainMaster) {
if (frmTrainMaster == null) {
throw new ArgumentNullException(nameof(frmTrainMaster));
}
_frmTrainMaster = frmTrainMaster;
}
}
Or via setter injection:
public class AnotherForm : Form {
private FrmTrainMaster _frmTrainMaster;
public FrmTrainMaster MasterForm { set { _frmTrainMaster = value; } }
}
Either way the reflection is not necessary at all. At any place in your AnotherForm you can just call
class AnotherForm : Form {
...
public void FooMethodThatCallFillValues() {
_frmTrainMaster.FillValues("...");
}
}