I have data as listed below in database.
I have following data access layer code that is working for simple scenarios.. But for the above scenario, I need result based on employeeID grouping.. All roles for an employee should come under one Employee object.
How can we achieve this by modifying the following data access code using the generic delegate features of C# ?
Note: I am looking for a solution that does not use DataTable (since DataTable loads all data upfront and is slower than the IDataRecord approach).
REFERENCES
An Elegant C# Data Access Layer using the Template Pattern and Generics
Using C# generics and factory classes to map IDataReader to POCO
Data Transfer Object
public class Role
{
public int RoleID { get; set; }
public string RoleName { get; set; }
}
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public List<Role> Roles { get; set; }
//IDataRecord Provides access to the column values within each row for a DataReader
//IDataRecord is implemented by .NET Framework data providers that access relational databases.
//Factory Method
public static Employee EmployeeFactory(IDataRecord record)
{
return new Employee
{
EmployeeID = (int)record[0],
EmployeeName = (string)record[1]
};
}
}
Common DAL
public class MyCommonDAL
{
public static IEnumerable<T> ExecuteQueryGenericApproach<T>(string commandText, List<SqlParameter> commandParameters, Func<IDataRecord, T> factoryMethod)
{
string connectionString = #"Server=TRVMVSDDVXXXX;Database=AS400_Source;User Id=XXXXXXXX;Password=XXXXXXX";
//Action, Func and Predicate are pre-defined Generic delegates.
//So as delegate they can point to functions with specified signature.
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = commandText;
command.CommandTimeout = 0;
command.Parameters.AddRange(commandParameters.ToArray());
connection.Open();
using (var rdr = command.ExecuteReader())
{
while (rdr.Read())
{
yield return factoryMethod(rdr);
}
rdr.Close();
}
}
}
}
}
Specific DAL
public class MyEmployeeDAL
{
public List<Employee> GetEmployees(string excludedEmployee)
{
List<SqlParameter> commandParameters = new List<SqlParameter>()
{
new SqlParameter {ParameterName = "#ExcludedEmployee",
Value = excludedEmployee,
SqlDbType = SqlDbType.VarChar}
};
string commandText = #"SELECT E.EmployeeID,E.EmployeeName,R.RoleID,R.RoleName FROM dbo.EmployeeRole ER
INNER JOIN dbo.Employee E ON E.EmployeeID= ER.EmployeeID
INNER JOIN dbo.[Role] R ON R.RoleID= Er.RoleID
WHERE EmployeeName <> #ExcludedEmployee";
IEnumerable<Employee> employees = MyCommonDAL.ExecuteQueryGenericApproach<Employee>(commandText, commandParameters, Employee.EmployeeFactory);
return employees.ToList();
}
}
Client
static void Main(string[] args)
{
MyEmployeeDAL logDAL = new MyEmployeeDAL();
List<Employee> logSeverities = logDAL.GetEmployees("test");
}
You should add new flat class
public class RoleAndEmployee
{
public int RoleID { get; set; }
public string RoleName { get; set; }
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public static Employee EmployeeFactory(IDataRecord record)
{
return new RoleAndEmployee
{
EmployeeID = (int)record[0],
EmployeeName = (string)record[1],
RoleID = (int)record[2],
RoleName = (string)record[3]
};
}
}
and call (i hope, i write it correct without IDE):
IEnumerable<Employee> employees = MyCommonDAL.ExecuteQueryGenericApproach<RoleAndEmployee>(commandText, commandParameters, RoleAndEmployee.EmployeeFactory)
.GroupBy(c=>new {c.EmployeeId, c.EmployeeName}, c=>new{c.RoleId, c.RoleName})
.Select(k=>new Employee{EmployeeId=k.Key.EmployeeId, EmployeeName= k.Key.EmployeeName, Roles = k.ToList()});
Update:
If you don't want introduce flat class, you can use next approach:
public static Employee EmployeeFactory(IDataRecord record)
{
var employee = new Employee
{
EmployeeID = (int)record[0],
EmployeeName = (string)record[1],
Roles = new List<Role>()
};
employee.Roles.Add(new Role{RoleID = (int)record[2], roleName=(string)record[3]});
return employee;
}
IEnumerable<Employee> employees = MyCommonDAL.ExecuteQueryGenericApproach
<Employee>(commandText, commandParameters, Employee.EmployeeFactory)
.GroupBy(
x => new { x.EmployeeID, x.EmployeeName},
(key, group) =>
new Employee
{
EmployeeId=key.EmployeeID,
EmployeeName=key.EmployeeName,
Roles = group.SelectMany(v => v.Roles).ToList()
}).ToList();
For so to happen you need to assign values to Roles property of your Employee objects.
In factoryMethod you need to find distinct employees create object of the same and assign corresponding roles that you get from your query.
This may help you query your table that you have got as a result.
After executing logDAL.GetEmployees("test") in your specific DAL, just group them.
IEnumerable<Employee> employees = MyCommonDAL.ExecuteQueryGenericApproach
<Employee>(commandText, commandParameters, Employee.EmployeeFactory);
employees = employees.GroupBy(
x => new
{
x.EmployeeID,
x.EmployeeName
},
(key, groupedEmployees) =>
new Employee
{
EmployeeId=key.EmployeeID,
EmployeeName=key.EmployeeName,
Roles = groupedEmployees.SelectMany(v => v.Roles)
});
return employees.ToList();
Related
In my ASP.Net Core-6 Web API, I am implementing SqlClient in ADO.NET Core. I want to select employees by employment date.
I have this entity (table):
public class Employee
{
public int EmployeeId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string EmploymentDate { get; set; }
}
Then I created this stored procedure in the SQL Server DB:
CREATE PROCEDURE [dbo].[sp_employees]
#pdStartDate datetime,
#pdEndDate datetime
AS
SELECT
*
FROM
[Employees].[dbo].[employees]
WHERE
EmployementDate BETWEEN #pdStartDate AND #pdEndDate
RETURN 1
I want to spool employees between a range of selected employment date using ADO.NET Core SqlClient. I have written this code:
public IEnumerable<Employee> GetEmployees()
{
List<Employee> employeelist = new List<Employee>();
using (con = new SqlConnection(connection))
{
con.Open();
command = new SqlCommand("sp_employees", con);
command.CommandType = CommandType.StoredProcedure;
dataReader = command.ExecuteReader();
while (dataReader.Read())
{
Employee employee = new Employee();
employee.EmployeeId = Convert.ToInt32(dataReader["EmployeeId"]);
employee.Firstname = dataReader["Firstname"].ToString();
employee.Lastname = dataReader["Lastname"].ToString();
employee.Email = dataReader["Email"].ToString();
employee.EmploymentDate = Convert.ToDateTime(dataReader["EmploymentDate"].ToString());
employeelist.Add(employee);
}
con.Close();
}
return employeelist;
}
How do I modify the code above to include the StartDate and EndDate of the EmploymentDate in the stored procedure?
You could use:
con.Open();
command = new SqlCommand("sp_employees", con);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#pdStartDate", startDate);
command.Parameters.AddWithValue("#pdEndDate", endDate);
dataReader = command.ExecuteReader();
I want to know how to map the foreign key object when reading data using ADO.NET from a stored procedure. I am not able to map the response for Dependent which will be a list of objects to the response object. I have mapped the other fields into the response object. Please help me out here. I am new to ADO.NET.
These are my model classes
public class Employee{
public int Id {get; set;}
public string Name {get; set;}
//I don't know how to map response for this property.
public virtual ICollection<Dependent> Dependent {get; set; }
}
public class Dependent {
public int Id {get; set;}
public int EmployeeId {get; set;}
public string Name {get; set; }
}
Stored procedure
Create procedure spGetEmployeeAll
as
begin
Select * from Employee
inner join Dependent
on Employee.Id = Dependent.EmployeeId
end
Repository class
public Class EmployeeRepositroy : IEmployeeRespository{
private readonly string _connectionString;
public EmployeeRepository(IConfiguration configuration){
_connectionString = configuration.GetConnectionString("TestDatabase");;
}
public Employee GetAll(){
Employee response = new Employee ();
using (SqlConnection sql = new SqlConnection(_connectionString)) {
using (SqlCommand cmd = new SqlCommand("spGetEmployeeAll ", sql)) {
cmd.CommandType = System.Data.CommandType.StoredProcedure;
sql.Open();
using (var reader = cmd.ExecuteReader()) {
response.Id = Convert.ToInt32(reader["ID"]);
respone.Name = Convert.ToString(reader["Name"]);
//How to map Dependent details into the response object?
}
sql.close();
}
}
return response;
}
}
It's often easier to use two separate select statements, and map the child rows into the parent objects:
CREATE OR ALTER PROCEDURE GetEmployeeAll
AS
SELECT
Id,
Name
FROM Employee;
SELECT
Id,
EmployeeId,
Name
FROM Dependent
GO
You can map them in using a dictionary
public IEnumerable<Employee> GetAll()
{
var dict = new Dictionary<int, Employee>();
using (SqlConnection sql = new SqlConnection(_connectionString))
using (SqlCommand cmd = new SqlCommand("GetEmployeeAll", sql))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
sql.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
dict.Add((int)reader["ID"],
new Employee {
Id = (int)reader["ID"],
Name = (string)reader["Name"],
});
}
reader.NextResult();
while (reader.Read())
{
dict[(int)reader["ID"]].Dependent.Add(
new Dependent {
Id = (int)reader["Id"],
EmployeeId = (int)reader["EmployeeId"],
Name = (string)reader["Name"],
});
}
}
}
return dict.Values;
}
I have a database that has two tables as follows, Please ignore the data but the format looks as follows
Now I have a Model class that is constructed as follows
public class FamilyModel
{
public string Name { get; set; }
public List<FamilyModel> FamilyList { get; set; }
public FamilyModel()
{
FamilyList = new List<FamilyModel>();
}
}
Now all I want is to get data from the two tables and populate the list.
So I have a stored procedure that returns data as follows
So I have written some code to populate the above class. But it dosent work. I get a count of 5 when I debug. I want the count to be 2 and when expanded I want something like FamilyA ->{Nick, Tom, Pam}.. FamilyB->{Harry} and so on. Please help fixing this code.
public static FamilyModel familyData()
{
//FamilyModel fml = new FamilyModel();
//fml.FamilyList = new List<FamilyModel>();
using (SqlConnection con = new SqlConnection(#"Data Source=(LocalDB)\v11.0; AttachDbFilename=|DataDirectory|\Families.mdf; Integrated Security=True; Connect Timeout=30;"))
{
con.Open();
SqlCommand cmd = new SqlCommand("sp_GetFamilies", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read()) {
FamilyModel fm = new FamilyModel();
fm.Name = dr["FamilyName"].ToString();
foreach (var item in dr["ChildName"].ToString())
{
if (Convert.ToInt32(dr["id"]) == Convert.ToInt32(dr["FID"]))
{
fm.FamilyList.Add(new FamilyModel() { Name = dr["ChildName"].ToString() });
}
}
}
return fm;
}
}
Here is some source code that should get the right idea across. Below it, I've included some explanation for what's going on.
using Dapper;
public class FamilyModel
{
public int Id { get; set;}
public string FamilyName { get; set; }
public List<Person> Members { get; set; } = new List<Person>();//Initializer for Auto-Property, C#6<= only
}
public class Person
{
public int Id { get; set;}
public string Name { get; set; }
}
public class DatabasePOCO
{
public string FamilyName { get; set; }
public string ChildName { get; set; }
public int Fid { get; set; }
public int Id { get; set;}
}
void Main()
{
using (IDbConnection conn = new SqlConnection("..."))
{
conn.Open();
var raw = conn.Query<DatabasePOCO>("sp_GetFamilies",
commandType: CommandType.StoredProcedure);//Could be dynamic, not typed
var familyList = raw
.GroupBy(x => x.Fid)
.Select(x =>
{
var rawMembers = x.ToList();
var fId = x.First().Fid;
var fName = x.First().FamilyName;
var members = rawMembers.Select(y => new Person
{
Id = y.Id,
Name = y.ChildName
});
return new FamilyModel
{
Id = fId,
FamilyName = fName,
Members = members.ToList()
};
});
//Whatever else you want to do here
}
}
Consider using Dappper. It is a great ORM that makes accessing data from database really easy. It's designed to work with SQL Server, but I've had success using Oracle too, and most other RMDBS systems will probably work.
Consider using Slapper. If you have control over your stored procedure, this can reduce a lot of the boilerplate code below.
If you use Dapper (I hope you do), you can play around with C# dynamic objects, or you can create a POCO to help get some type enforcement on your code.
Understand if you care about reference equality. The code I provided below does not enforce reference equality of objects. Reference equality, in my experience, doesn't buy you much and is a pain to enforce.
You need to distinguish between a new row in the data set and a new FamilyModel. One way to do this is to declare a list of models, then look up the "right" one before you add the current child row:
var rootModel = new FamilyModel();
rootModel.Name = "root";
// ... Set up data reader ...
while (dr.Read())
{
//This requires support for the Id in your FamilyModel:
var id = (int)dr["Id"];
//You could also use ".Single(...)" here
var fm = rootModel.FamilyList.Where(x => x.Id == id).First();
if (fm == null)
{
fm = new FamilyModel();
fm.Name = dr["FamilyName"].ToString();
rootModel.FamilyList.Add(fm);
}
fm.FamilyList.Add(new FamilyModel() { Name = dr["ChildName"].ToString() });
}
For each row in your database query, you'll:
Try to look up that family in your list
If you don't find one, create a new one. Add it to your top-level list.
Add the child name as a sub-element of the "current" family.
I have a web service running on a local server. In it I have a method that returns a json representation of an Oracle database query, which is converted from a List<Dictionary<String, String>> object.
I have a GUI program that needs the same data. If I try to move the query to the GUI, I have to require the user to install Oracle on there system, according to what I read after getting the following error:
Could not load file or assembly 'Oracle.DataAccess, Version=...'
I don't want to make my users install Oracle on their system.
My solution has been to just access the existing logic running on a web service (.NET 3.5 WebForms App). I wrote another web service that returns the List<Dictionary<String, String>> object, instead of going through the hassle of converting it to a json object.
How can I get this from the GUI?
In my initial searches I found a few links that said it was possible, or some where they returned an object in json format. But nothing for what I was looking for explicitly.
Any help?
not quite following you but how about a web api? here is one I was playing around with using the hr schema. so create a web api project. then open the tools library package manager and add the oracle driver. type in Install-Package odp.net.managed then configure your connection string in your web config here is mine just a database on my local machine so I'll leave the password in.
<add name="hr" connectionString="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=mydatabase))); User Id=hr; Password=fdsafdsafafads;" providerName="oracle.manaagedatacess.client" />
then create your model/s here is one employees
public class employee{
public int employee_id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string phone_number { get; set; }
public DateTime hire_date { get; set; }
public string job_id { get; set; }
public decimal? salary { get; set; }
public decimal? commission_pct { get; set; }
public int? manager_id { get; set; }
public int? department_id { get; set; }
}
I then made a repository (if you use entity framework as opposed to ado you don't need these steps but I don't typically use entity framework)
namespace webApiOracle.Models
{
internal class employeeRepository
{
internal static IEnumerable<employee> getAllEmployees()
{
List<employee> employees = new List<employee>();
string sql = "SELECT * from employees";
OracleDataReader rdr = dataHelper.getrdr(sql);
if (rdr.HasRows)
{
while (rdr.Read())
{
employee emp = getEmployee(rdr);
employees.Add(emp);
}
rdr.Close();
}
return employees;
}
internal static employee getEmployee(int id)
{
employee emp = null;
string sql = "SELECT * from employees where employee_id = " + id;
OracleDataReader rdr = dataHelper.getrdr(sql);
if (rdr.HasRows)
{
while (rdr.Read())
{
emp = getEmployee(rdr);
}
rdr.Close();
}
return emp;
}
internal static employee add(employee emp)
{
OracleDataAdapter oda = new OracleDataAdapter();
string sql = "Insert into employees ";
sql = sql + "(EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID) ";
sql = sql + "values (EMPLOYEES_SEQ.NEXTVAL,:first_name,:last_name,:email,:phone_number,sysdate,:job_id,:salary,:commission_pct,:manager_id,:department_id)";
OracleConnection cn =
new OracleConnection(
ConfigurationManager.ConnectionStrings
["hr"].ConnectionString);
cn.Open();
oda.InsertCommand = new OracleCommand(sql, cn);
oda.InsertCommand.BindByName = true;
oda.InsertCommand.Parameters.Add(":first_name", emp.first_name);
oda.InsertCommand.Parameters.Add(":last_name", emp.last_name);
oda.InsertCommand.Parameters.Add(":email", emp.email);
oda.InsertCommand.Parameters.Add(":phone_number", emp.phone_number);
oda.InsertCommand.Parameters.Add(":job_id", emp.job_id);
oda.InsertCommand.Parameters.Add(":salary", emp.salary);
oda.InsertCommand.Parameters.Add(":commission_pct", emp.commission_pct);
oda.InsertCommand.Parameters.Add(":manager_id", emp.manager_id);
oda.InsertCommand.Parameters.Add(":department_id", emp.department_id);
int count = oda.InsertCommand.ExecuteNonQuery();
sql = "SELECT * from employees where employee_id = EMPLOYEES_SEQ.CURRVAL";
OracleDataReader rdr = dataHelper.getrdr(sql);
if (rdr.HasRows)
{
while (rdr.Read())
{
emp = getEmployee(rdr);
}
rdr.Close();
}
return emp;
}
internal static IEnumerable<employee> getEmployeesByVal(string key, string value)
{
List<employee> employees = new List<employee>();
string sql = "SELECT * from employees where " + key + " = '" + value + "'";
OracleDataReader rdr = dataHelper.getrdr(sql);
if (rdr.HasRows)
{
while (rdr.Read())
{
employee emp = getEmployee(rdr);
employees.Add(emp);
}
rdr.Close();
}
return employees;
}
private static employee getEmployee(OracleDataReader rdr)
{
employee emp = new employee
{
employee_id = rdr.GetInt32(rdr.GetOrdinal("EMPLOYEE_ID")),
first_name = rdr["FIRST_NAME"].ToString(),
last_name = rdr["LAST_NAME"].ToString(),
email = rdr["EMAIL"].ToString(),
phone_number = rdr["PHONE_NUMBER"].ToString(),
hire_date = rdr.GetDateTime(rdr.GetOrdinal("HIRE_DATE")),
job_id = rdr["JOB_ID"].ToString(),
salary = dataHelper.decimalnullable(rdr, "SALARY"),
commission_pct = dataHelper.decimalnullable(rdr, "COMMISSION_PCT"),
manager_id = dataHelper.intnullable(rdr, "MANAGER_ID"),
department_id = dataHelper.intnullable(rdr, "DEPARTMENT_ID")
};
return emp;
}
}
}
then make a web api controller go to the controller folder right click and pick empty web api controller.
public class employeeController : ApiController
{
public IEnumerable<employee> GetAllEmployees()
{
return employeeRepository.getAllEmployees();
}
//[Route("api/employee/{id:int}")]
public employee getEmployee(int id) {
return employeeRepository.getEmployee(id);
}
[Route("api/employee/{key}/{value}")]
public IEnumerable<employee> getEmployeesByVal(string key, string value) {
return employeeRepository.getEmployeesByVal(key, value);
}
[HttpPost]
public employee add(employee emp)
{
return employeeRepository.add(emp);
}
}
}
then when you want your json data just call the url so if I want all employees my url is /api/employee if you want just one employee it with id of 100 would be /api/employee/100 if you want employees with last name king it would be /api/employee/last_name/king
there are tons of web api examples out there and a couple free ebooks if you want to check it out.
I have a function that gets the data in the database. Below are the code.
public DataTable getAllTransaction(OleDbConnection conn)
{
OleDbDataAdapter oleAdapter = new OleDbDataAdapter();
string query = "";
DataTable tblResult = new DataTable();
query = #"SELECT t.id AS `Transaction ID`,
c.id AS `Client ID`,
c.clientname AS `Client Name`,
t.cashvalue AS `Cash Value`,
t.amount AS `Amount`,
t.transdate AS `Transaction Date`,
t.remarks AS `Remarks`
FROM client AS c
INNER JOIN `transaction` AS t
ON c.id=t.clientid";
oleAdapter.SelectCommand = new OleDbCommand(query, conn);
oleAdapter.Fill(tblResult);
return tblResult;
}
My problem is, how could I store the result set into model (e.g. I don't want to return DataTable). Below is my Model Class.
Class TransactionModel
{
public int transID { get; set; }
public int clientID { get; set; }
public string clientName { get; set; }
public double cashValue { get; set; }
public double amout { get; set; }
public DateTime transDate { get; set; }
public string remarks { get; set; }
}
You could use LINQ and do:
var tranModel = from r in tblResult.Tables[0]
select new TransactionModel
{
transId = r.Field<int>("transID"),
clientId = r.Field<int>("clientId"),
clientName = r.Field<string>("ClientName")
}
Note since you are using .NET 2.0. LINQ is not directly available. You will have to use
Something like LINQBridge: http://www.albahari.com/nutshell/linqbridge.aspx
Another alternative is to loop through all of the rows in tblResult and have a generic list of TransactionModel. For instance:
List<TransactionModel> tModels = new List<TransactionModel>();
foreach (var row in tblResult.Tables[0].Rows)
{
tModels.Add(new TransactionModel
{
transId = row["TransId"],
clientId = row["ClientId"],
clientName = row["clientName"]
});
}
Because LINQ isn't available in .NET 2, you'd have to loop through the items yourself and transform them into your type. Something like this:
DataTable transactions = getAllTransactions();
List<TransactionModel> model = new List<TransactionModel>();
foreach (DataRow transaction in transactions.Rows)
{
TransactionModel tran = new TransactionModel
{
transId = transaction.Field<int>("transID"),
clientId = transaction.Field<int>("clientId"),
clientName = transaction.Field<string>("ClientName")
//etc...
};
model.Add(tran);
}
Do something like:
List<TransactionModel> TransactionItems = tblResult.AsEnumerable().Select(r =>
new TransactionModel
{
transID = r.Field<int>("TransactionID"),
clientID = r.Field<int>("clientID"),
and so on.....
}).ToList();
return items;