Populate Model class from Data in Backend - c#

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.

Related

Infinite Loop When Loading Object

I've been racking my brains for a few days, trying not to generate infinite loops when I load my classes with data coming from a database through a method inside the class.
In a playful example this is what is happening. Consider the class below:
public class Person
{
public int id { get; set; }
public string name{ get; set; }
public Person carrier{ get; set; }
}
In the method inside the class I defined it like this:
public void Load()
{
Datatable tableResult;
using (MySqlConnection con = new MySqlConnection(ConnectionString))
{
using (MySqlCommand com = new MySqlCommand(My.Resources.PersonSelect))
{
com.Parameters.AddWithValue("#ID", this.ID);
using (MySqlDataAdapter adp = new MySqlDataAdapter(CmdPerson))
{
TableResult = new DataTable();
adp.Fill(TableResult);
if (tableResult.Rows.Count == 1)
{
ID = tableResult.Rows(0).Item("id");
Name = tableResult.Rows(0).Item("name");
Carrier = new Person().Load(tableResult.Rows(0).Item("carrierid"));
}
}
}
}
}
Also consider that I have a person, and that person's carrier is himself. this would generate an infinite loop.
I've already tried implementing LazzyLoading in the properties, but I still don't know which classes might have this same problem. Is there any other way to solve this problem?
You can try this:
public void Load()
{
var tableResult = new DataTable();
using (var con = new MySqlConnection(ConnectionString))
using (var com = new MySqlCommand(My.Resources.PersonSelect))
using (var adp = new MySqlDataAdapter(com))
{
com.Parameters.AddWithValue("#ID", this.ID);
adp.Fill(tableResult);
}
if (tableResult.Rows.Count == 1)
{
ID = tableResult.Rows(0).Item("id");
Name = tableResult.Rows(0).Item("name");
if (ID == tableResult.Rows(0).Item("carrierid"))
{
Carrier = this;
}
else
{
Carrier = new Person().Load(tableResult.Rows(0).Item("carrierid"));
}
}
}
It still has potential to have a loop in a chain (Person A's carrier is Person B, who's carrier is Person A again), but if that's not a situation in your data you might be okay.
Otherwise you can opt to lazy-load the carrier, like this:
public class Person
{
public int ID { get; set; }
public string Name{ get; set; }
private int _carrierID;
private Person _carrier = null;
public Person Carrier
{
get
{
if (_carrier == null) _carrier = Person.Load(_carrierID);
return _carrier;
}
private set
{
_carrier = value;
if (_carrier != null) _carrierID = _carrier.ID;
}
}
public static Person Load(int ID)
{
var tableResult = new DataTable();
using (var con = new MySqlConnection(ConnectionString))
using (var com = new MySqlCommand(My.Resources.PersonSelect))
using (var adp = new MySqlDataAdapter(com))
{
com.Parameters.AddWithValue("#ID", ID);
adp.Fill(tableResult);
}
if (tableResult.Rows.Count == 1)
{
return new Person() {
ID = tableResult.Rows(0).Item("id"),
Name = tableResult.Rows(0).Item("name"),
_carrierid = tableResult.Rows(0).Item("carrierid")
};
}
return null;
}
}
This can also still possibly loop forever if you try to write code like this:
var p = Person.Load(someID);
while (p.Carrier!=null)
{
p = p.Carrier;
}
But at least now you have to put the loop out in the open where you can find it.
Also note how I converted the Load() method to static.

Working with multiple rows in table as properties

I'm working with sqlite databases using System.Data.Sqlite library. I have the "metadata" table, which looks like this:
Table screenshot
It doesn't always contain all of these rows and fields, and there can also be a lot of optional ones.
Usually I work with these fields as properties (which I get through reading queries and reflection), my class:
class MetadataTable
{
public string Version { get; set; }
public string Timestamp { get; set; }
public string Author { get; set; }
public string Location { get; set; }
public MetadataTable(string pathToDb)
{
using (SQLiteConnection connection = new SQLiteConnection($"Data Source={pathToDb};Version=3;"))
{
try
{
connection.Open();
}
catch (Exception)
{
throw new Exception("Unable to open database.");
}
using (SQLiteCommand command = new SQLiteCommand(connection))
{
command.CommandText = "SELECT key, value FROM metadata;";
using (SQLiteDataReader reader = command.ExecuteReader())
{
List<string> propertyNames = new List<string>();
List<string> propertyValues = new List<string>();
while (reader.Read())
{
propertyNames.Add(reader[0].ToString());
propertyValues.Add(reader[1].ToString());
}
for (int i = 0; i < propertyNames.Count; i++)
propertyNames[i] = propertyNames[i].ToLower().First().ToString().ToUpper()
+ propertyNames[i].Substring(1);
for (int i = 0; i < propertyValues.Count; i++)
typeof(MetadataTable).GetProperty(propertyNames[i])?.SetValue(this, propertyValues[i]);
}
}
}
}
}
And how it's called from Main():
string pathToDb = "D:/Downloads/mytest.db";
MetadataTable metadataTable = new MetadataTable(pathToDb);
Console.WriteLine($"Version:{metadataTable.Version}, Author:{metadataTable.Author}, " +
$"Timestamp:{metadataTable.Timestamp}, Location:{metadataTable.Location ?? "Not specified"}");
Recently I decided to try LINQ to SQL and written a simple class for my table:
[Table(Name = "metadata")]
class Metadata
{
[Column(Name = "key")]
public string Key { get; set; }
[Column(Name = "value")]
public string Value { get; set; }
}
That's how I read it in Main():
using (SQLiteConnection connection = new SQLiteConnection($"Data Source={pathToDb};Version=3;"))
{
using (DataContext context = new DataContext(connection))
{
Table<Metadata> metadataFields = context.GetTable<Metadata>();
foreach (Metadata metadataField in metadataFields)
Console.WriteLine($"Key:{metadataField.Key}, Value:{metadataField.Value}");
}
}
I find it very convinient, but is there same convinient way to work with rows/fields as properties (like in MetadataTable code above) without using reflection?
And BTW, is EntityFramework more suitable (and more performant?) for that task? I didn't work with LINQ to Entity or LINQ to SQL before, so it doesn't make big difference what to learn first.

Displaying self-referencing table's data in a c# application

I have following table:
---------------------
Id Title Parent
---------------------
1 Parent NULL
2 Level_1 1
3 Level_2 1
4 Level_3 1
5 Level NULL
6 Level_New 5
Now I want to display these data in my console application, I know I need a recursive function but no idea how to do it becuase I want to read these data using ADO.NET not EntityFramework.In EF I could define a model that has a navigation property for children:
public class Menu
{
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; set; }
}
But the problem is that I don't want to use EF. I want to do it using raw ADO.NET
Recursion isn't fun, this is a solution that I used to test for a much larger recursion
public class MyObject
{
public string Id;
public string ParentId;
public string Name;
public string Comments;
}
a lot of this code you wont need, but this should give you want you need on recursion.
private void BindTree(IEnumerable<MyObject> list, TreeNode parentNode, string previousNode)
{
var myObjects = list as IList<MyObject> ?? list.ToList();
var nodes = myObjects.Where(x => (parentNode == null ? x.ParentId == "[].[].[(root)]" : x.ParentId == parentNode.Value));
var listOfNodeNames = new List<string>();
foreach (var node in nodes)
{
var newNode = new TreeNode(node.Name, node.Id);
BindTree(myObjects, newNode, previousNode);
}
}
The above code does the recursion I need ( code you wont need stripped out ) and builds a treeview on a page based on data from a datatable.
But, this should give you want you need to do your recursion.
You need to pull data from server first, then construct tree on client side. Beware of circular reference.
First, change your Menu class to ensure that Children will never null
public class Menu
{
public Menu()
{
Children = new HashSet<Menu>();
}
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; private set; }
}
Then pull the data from database, and construct the tree
var connBuilder = new SqlConnectionStringBuilder();
connBuilder.DataSource = "localhost";
connBuilder.InitialCatalog = "YourDatabaseName";
connBuilder.IntegratedSecurity = true;
using (var con = new SqlConnection(connBuilder.ToString()))
{
con.Open();
var list = new List<Menu>();
//pull data from database
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT Id, Title, Parent FROM [dbo].[YourTableName]";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(new Menu
{
Id = reader.GetInt32(0),
Title = reader.GetString(1),
Parent = reader.IsDBNull(2) ?(int?) null : reader.GetInt32(2)
});
}
}
}
//construct tree
var newList = new List<Menu>();
foreach (var l1 in list)
{
if (l1.Parent == null)
{
newList.Add(l1);
}
foreach (var l2 in list)
{
if (l2.Parent == l1.Id)
{
l1.Children.Add(l2);
}
}
}
// do whatever you want with newList
}
You will get data like this

Grouping IDataRecord individual records to a collection

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();

Query on two inherited classes in EF Code First

I'm using EF code first and I have two classes that inherited from a base class(TPT):
public class Party
{
public int PartyId {get; set;}
}
public Person:Party
{
string FirstName { get; set; }
string LastName { get; set; }
}
public Organization:Party
{
string Name { get; set; }
}
Now, I want to create a query to fetch all Persons that their LastNames are equal to "SomeName" and all Organizations that their Name begins with "A" in one transaction.
Something like this
IList<Party> GetParties(string name, string organizationName)
{
IList<Party> result = new List<Party>();
using(var context = new MyContext())
{
var persons = context.Parties.OfType<Person>().Where(t=>t.LastName = name) ;
var organizations = context.Parties.OfType<Organization>().Where(t=>t.Name.StartWith(organizationName));
//return merge of persons and organizations
}
}
Is there any way to do this?
You can do
context.Parties.OfType<Person>().Where(t=>t.LastName = name).OfType<Party>()
.Concat(context.Parties.OfType<Organization>()
.Where(t=>t.Name.StartWith(organizationName)))
You don't have to cast the second collection to Party because it is concatenated with an IQueryable<Party> which is covariant with IQueryable<Organization>.
I did some LINQPad fiddling and I believe that this is the result you want.
IList<Party> GetParties(string name, string organizationName)
{
IList<Party> result = new List<Party>();
using(var context = new MyContext())
{
var persons = context.Parties.OfType<Person>().Where(t=>t.LastName = name) ;
var organizations = context.Parties.OfType<Organization>().Where(t=>t.Name.StartWith(organizationName));
//return merge of persons and organizations
result = (IList<Party>)persons.Union((IEnumerable<Party>)organizations);
}
return result;
}

Categories

Resources