What I am attempting to do, is to be able to use complex objects both for retrieval and for writing back to the database.
For example, I have two classes
[Table("Children")]
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Data { get; set; }
}
[Table("Parents")]
public class Parent
{
public int Id { get; set; }
public string Data { get; set; }
public List<Child> Children { get; set; }
public Parent()
{
Children = new List<Child>();
}
}
I populate the Parent object using the method below
public List<Parent> GetAll()
{
var parents = new List<Parent>();
using (SQLiteConnection conn = new SQLiteConnection(sqlConnectionString))
{
string sql = $#"
SELECT * From Parents;
SELECT * FROM Children;
;";
var results = conn.QueryMultiple(sql);
parents = results.Read<Parent>().ToList();
var children = results.Read<Child>();
foreach (var parent in parents)
{
parent.Children = children.Where(a => a.ParentId == parent.Id).ToList();
}
}
return parents;
}
My Question now that I have my populated object, say I want to make changes to Parent including adding/updating/remove values from Parent.Children
This is my first pass at an update method, but I recognize there are issues here, such as making the determination to update or insert based on the child Id being zero or not, as well as this being a bit verbose.
Is there way, and am I missing some functionality of Dapper or Dapper.Contrib that would provide helper methods to make this process easier?
public bool Update(Parent parent)
{
bool result = true;
using (SQLiteConnection conn = new SQLiteConnection(sqlConnectionString))
{
conn.Open();
using (var tran = conn.BeginTransaction())
{
try
{
if (!conn.Update<Parent>(parent))
{
result = false;
}
foreach (var element in parent.Children)
{
if (element.Id == default(int))
{
conn.Insert<Child>(element);
}
else
{
if (!conn.Update<Child>(element))
{
result = false;
}
}
}
tran.Commit();
}
catch(Exception ex)
{
//logger.Error(ex,"error attempting update");
tran.Rollback();
throw;
}
}
}
return result;
}
Sadly, given how poor the response was to this question, and Dapper.Contrib questions in general. I've decided to dump Dapper and switch to an ORM that better suites my needs. Thanks for all those that offered suggestions.
Related
I've read a couple posts on why lazy loaded async properties are pointless and that a method should be used instead.
In my example code below... when not async... when I assign the list of parent objects to a datagridview control, the control knows to cascade down into the child objects. My inclination would be to make the child list getter asynchronous to maintain this relationship; however, posts I'm reading say that this should turn into an asynchronous method instead of being in a "getter".
If I turn the child list into a method then, the datagrid no longer knows the relationship exists and to cascade down to the child list.
I would like to either...
Change the retrieval of child objects to a method but still maintain the relationship
Implement a lazy child list property (apparently "bad"?)
Thanks in advance for any support you can provide helping me to understand this.
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
}
private async void FrmMain_Load(object sender, EventArgs e)
{
DataGrid parentsGrid = new DataGrid(); // << i am using a 3rd party grid control here that knows to cascade down into child objects
parentsGrid.DataSource = await Parents.FindAllAsync(); // << because of the List<Children> property, grid understands how to cascade through the relationships
}
}
public class Parents
{
public Guid ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
private List<Children> childrenList;
public List<Children> ChildrenList //how do i do a lazy instantation here and/or why is it "bad"
{
get
{
if (childrenList == null) { childrenList = Children.FindByParentIDAsync(ID).Result; } // << i know not async, question is how to make
return childrenList;
}
set { childrenList = value; }
}
public static async Task<List<Parents>> FindAllAsync()
{
List<Parents> pl = new List<Parents>();
try
{
using (SqlConnection sqlConnection = new SqlConnection("myConnectionString"))
{
using (SqlCommand sqlCommand = new SqlCommand("SELECT * FROM Parents", sqlConnection))
{
sqlCommand.CommandType = CommandType.Text;
await sqlCommand.Connection.OpenAsync().ConfigureAwait(false);
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
{
while (await sqlDataReader.ReadAsync())
{
pl.Add(readData(sqlDataReader));
}
}
}
}
}
catch (Exception ex) { throw ex; }
return pl;
}
private static Parents readData(SqlDataReader sqlr)
{
//eto mappings happen here, no need to show code...
return new Parents();
}
}
public class Children
{
public Guid ID { get; set; }
public Guid ParentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public async static Task<List<Children>> FindByParentIDAsync(Guid ParentID)
{
List<Children> pl = new List<Children>();
try
{
using (SqlConnection sqlConnection = new SqlConnection("myConnectionString"))
{
using (SqlCommand sqlCommand = new SqlCommand("SELECT FROM Children WHERE ParentID = #ParentID", sqlConnection))
{
sqlCommand.CommandType = CommandType.Text;
sqlCommand.Parameters.Add("#ParentID", SqlDbType.UniqueIdentifier).Value = ParentID;
await sqlCommand.Connection.OpenAsync().ConfigureAwait(false);
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
{
while (await sqlDataReader.ReadAsync())
{
pl.Add(readData(sqlDataReader));
}
}
}
}
}
catch (Exception ex) { throw ex; }
return pl;
}
private static Children readData(SqlDataReader sqlr)
{
//eto mappings happen here, no need to show code...
return new Children();
}
}
interface Nameable
{
string Name { get; set; }
}
class Parent : Nameable
{
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
class Child
{
public string Name { get; set; }
public int Value { get; set; }
public string DataOne { get; set; }
public string DataTwo { get; set; }
public double DataThree { get; set; }
}
static async void MainAsync(string[] args)
{
for (int i = 0; i < random.Next(10000, 50000); i++)
{
Parents.Add(CreateParent());
}
Parents = Parents.GroupBy(g => g.Name).Select(grp => grp.First()).ToList();
foreach (var parent in Parents)
{
await Insert<Parent>(parent);
}
// update objects randomly;
foreach (var parent in Parents)
{
for (int i = 0; i < random.Next(10, 30); i++)
{
int decision = random.Next(0, 2);
if (decision == 0 && parent.Children.Count > 0)
{
parent.Children.RemoveAt(random.Next(0, parent.Children.Count));
}
else
{
var inner = CreateChild();
if (!parent.Children.Any(io => io.Name == inner.Name))
{
parent.Children.Add(inner);
}
}
await ReplaceOne<Parent>(parent);
}
}
}
I have a list of Parents and each one contains a list of Child elements. When using the c# Mongo driver to replace these parents after they have been updated by either removing or adding new Children It sometimes creates duplicates of the Child on the Mongo side despite there being no duplicates when the code calls the replace method.
I think this is something to do with the atomic sub document structure of Mongo and how it updates/replaces items. Is there a way to prevent this from creating duplicates? and if it is not happening due to the atomic nature what is causing this?
Edit:
static async Task ReplaceOne<T>(T obj)
where T : Nameable
{
await database.GetCollection<T>(typeof(T).Name).ReplaceOneAsync(Builders<T>.Filter.Where(t => t.Name == obj.Name), obj);
}
static async Task Insert<T>(T obj)
{
await database.GetCollection<T>(typeof(T).Name).InsertOneAsync(obj);
}
static Parent CreateParent()
{
var innerObjects = new List<Child>();
for (int i = 0; i > random.Next(1, 10); i++)
{
innerObjects.Add(CreateChild());
}
return new Parent()
{
Name = RandomString(),
Children = innerObjects
};
}
static Child CreateChild()
{
return new Child()
{
Name = RandomString(),
Value = RandomInt(),
DataOne = RandomString(),
DataTwo = RandomString(),
DataThree = RandomDouble()
};
}
Added the replace/Insert snippets, they are using the mongo c# driver to insert into the db. The CreateParent and CreateChild just fills the objects with random relevant data.
I tried to guess your RandomString(), RandomInt() and RandomDouble() methods and I ran your project several times without cleaning the database. I could not detect any duplicates whatsoever based on the two "Name" properties (on parent and child).
I suspect your observation is somehow incorrect. In order to check if you do actually have duplicate children within the same parent you can use the following query:
collection.aggregate(
{
$unwind: "$Children"
},
{
$group:
{
_id:
{
"Name": "$Name",
"ChildName": "$Children.Name"
}
, "count": { $sum: 1 }
}
},
{
$match:
{
"count": { $ne: 1 } }
}
)
I have the following Model, which can be a child of the same type, and/or have many children of the same type.
public class SystemGroupModel
{
public int Id { get; set; }
public int? ParentSystemGroupModelId { get; set; }
[ForeignKey("ParentSystemGroupModelId")]
public virtual SystemGroupModel ParentSystemGroupModel { get; set; }
[InverseProperty("ParentSystemGroupModel")]
public virtual ICollection<SystemGroupModel> SubSystemGroupModels { get; set; }
}
How can I get a specific SystemGroupModel by Id that is at an unknown depth, and include all of it's children, grandchildren, great-grandchildren, etc.?
This appears to be working, which means it will build a list of any given parent, as well as children at an unlimited depth of grandchildren, great-grandchildren, and so on.
Is this the right approach?
First I created this new class
public class SystemGroupWorkingClass
{
public SystemGroupModel SystemGroupModel { get; set; }
public bool WasChecked { get; set; }
}
Then I added this code
EDIT: Updated to include the loop that builds List<SystemGroupWorkingClass>
List<SystemGroupWorkingClass> tempListSystemGroups = new List<SystemGroupWorkingClass>();
//Get the SystemGroups that were selected in the View via checkbox
foreach (var systemGroupVM in viewModel.SystemGroups)
{
if (systemGroupVM.Selected == true)
{
SystemGroupModel systemGroupModel = await db.SystemGroupModels.FindAsync(systemGroupVM.Id);
SystemGroupWorkingClass systemGroupWorkingClass = new SystemGroupWorkingClass();
systemGroupWorkingClass.SystemGroupModel = systemGroupModel;
systemGroupWorkingClass.WasChecked = false;
systemGroupWorkingClass.Selected = true;
//Make sure tempListSystemGroups does not already have this systemGroupWorkingClass object
var alreadyAddedCheck = tempListSystemGroups
.FirstOrDefault(s => s.SystemGroupModel.Id == systemGroupVM.Id);
if (alreadyAddedCheck == null)
{
tempListSystemGroups.Add(systemGroupWorkingClass);
}
}
}
for (int i = 0; i < tempListSystemGroups.Count; i++)
{
if (tempListSystemGroups[i].WasChecked == false)
{
SystemGroupModel systemGroupModel2 = await db.SystemGroupModels.FindAsync(tempListSystemGroups[i].SystemGroupModel.Id);
//Get the children, if there are any, for the current parent
var subSystemGroupModels = systemGroupModel2.SubSystemGroupModels
.ToList();
//Loop through the children and add to tempListSystemGroups
//The children are added to tempListSystemGroups as it is being iterated over
foreach (var subSystemGroupModel in subSystemGroupModels)
{
SystemGroupModel newSystemGroupModel = await db.SystemGroupModels.FindAsync(subSystemGroupModel.Id);
SystemGroupWorkingClass subSystemGroupWorkingClass = new SystemGroupWorkingClass();
subSystemGroupWorkingClass.SystemGroupModel = newSystemGroupModel;
subSystemGroupWorkingClass.WasChecked = false;
tempListSystemGroups.Add(subSystemGroupWorkingClass);
}
}
//Mark the parent as having been checked for children
tempListSystemGroups[i].WasChecked = true;
}
I have am creating an IEnumerable from a stream by creating each record one by one. Is there a way to check my current results if the Id already exists then I don't want to add the currently read record into the set.
public class Employee
{
public string Name { get; set; }
public string Country { get; set; }
public string Rank { get; set; }
public string Hours { get; set; }
public static Employee Create(IDataRecord record)
{
return new Employee
{
Name = record["name"],
Country = record["country"],
Rank = record["rank"],
Hours = record["hours"],
};
}
}
public IEnumerable<Employee> GetEmployees(TextReader stream)
{
using (var reader = MyLibraryFunction(stream)
{
while (reader.Read())
{
yield return Employee.Create(reader);
}
}
}
var result = GetEmployees(stream);
Edit: This is a simplified example. I actually want to know if I can access the existing records in the set. If it already exists, I would like to simply update one field (add the number of hours) in Employee, if it does not exist then it should be added.
Use hashset of all yielded object ID`s :
public IEnumerable<Employee> GetEmployees(TextReader stream)
{
var exisiting = new HashSet<int>();
using (var reader = MyLibraryFunction(stream)
{
while (reader.Read())
{
var employee = Employee.Create(reader);
if (exisiting.Add(employee.Id)) {
yield return employee;
}
}
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
The title is a little prosaic, I know. I have 3 classes (Users, Cases, Offices). Users and Cases contain a list of Offices inside of them. I need to compare the Office lists from Users and Cases and if the ID's of Offices match, I need to add those IDs from Cases to the Users. So the end goal is to have the Users class have any Offices that match the Offices in the Cases class.
To add clarity, I am looking to compare the two officeLists (users.officeList and cases.OfficeList) and when any Cases.OfficeList.ID == Users.OfficeList.ID, I need to add that Case.ID to the list of Users.caseAdminIDList
Any ideas?
My code (which isnt working)
foreach (Users users in userList)
foreach (Cases cases in caseList)
foreach (Offices userOffice in users.officeList)
foreach (Offices caseOffice in cases.officeList)
{
if (userOffice.ID == caseOffice.ID)
users.caseAdminIDList.Add(cases.adminID);
}//end foreach
//start my data classes
class Users
{
public Users()
{
List<Offices> officeList = new List<Offices>();
List<int> caseAdminIDList = new List<int>();
ID = 0;
}//end constructor
public int ID { get; set; }
public string name { get; set; }
public int adminID { get; set; }
public string ADuserName { get; set; }
public bool alreadyInDB { get; set; }
public bool alreadyInAdminDB { get; set; }
public bool deleted { get; set; }
public List<int> caseAdminIDList { get; set; }
public List<Offices> officeList { get; set; }
}
class Offices
{
public int ID { get; set; }
public string name { get; set; }
}
class Cases
{
public Cases()
{
List<Offices> officeList = new List<Offices>();
ID = 0;
}//end constructor
public int ID { get; set; }
public string name { get; set; }
public int adminID { get; set; }
public bool alreadyInDB { get; set; }
public bool deleted { get; set; }
public List<Offices> officeList { get; set; }
}
//in my main method
private bool grabCasesFromAdminDB() //gets cases from DB1 (AdminDB)
{
DatabaseIO dbIO = new DatabaseIO();
caseList = dbIO.getCasesFromAdminDB(adminDBConString, caseList).ToList();
if (dbIO.errorMessage == null || dbIO.errorMessage.Equals(""))
{
if (getCaseOfficeRelationship())
return true;
else
return false;
}//end if
else
{
MessageBox.Show(dbIO.errorMessage, "Admin DB Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}//end else
}//end method
private bool grabCasesFromListDB()//grabs cases from the main db
{
DatabaseIO dbIO = new DatabaseIO();
caseList = dbIO.getCasesFromMainDB(connectionString, caseList).ToList();
if (dbIO.errorMessage == null || dbIO.errorMessage.Equals(""))
return true;
else
{
MessageBox.Show(dbIO.errorMessage, "Main DB Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}//end else
}//end method
private bool getCaseOfficeRelationship()//adds office relationship to cases
{
DatabaseIO dbIO = new DatabaseIO();
caseList = dbIO.getOfficeToCaseRelationship(connectionString, caseList).ToList();
if (dbIO.errorMessage == null || dbIO.errorMessage.Equals(""))
{
if (getOfficeNamesByID())
return true;
else
return false;
}//end if
else
{
MessageBox.Show(dbIO.errorMessage, "Cases To Offices Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}//end else
}//end method
private bool getOfficeNamesByID()//grabs the id of the offices by name
{
List<Offices> officeList = new List<Offices>();
DatabaseIO dbIO = new DatabaseIO();
officeList = dbIO.getOfficeNamesByOfficeID(connectionString).ToList();
if (dbIO.errorMessage == null || dbIO.errorMessage.Equals(""))
{
matchOfficeNamesToIDs(officeList);
return true;
}//end if
else
{
MessageBox.Show(dbIO.errorMessage, "Getting Office List Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}//end else
}//end method
private void matchOfficeNamesToIDs(List<Offices> officeList)
{
foreach (Cases cases in caseList)
if (cases.officeList != null)
foreach (Offices office in cases.officeList)
{
foreach (Offices innerOffice in officeList)
if (innerOffice.ID == office.ID)
office.name = innerOffice.name;
}//end foreach
}//end method
//an example of my DBIO class
public List<Cases> getCasesFromAdminDB(string adminDBConString, List<Cases> caseList)
{
try
{
Cases cases = null;
SqlConnection con = new SqlConnection();
con.ConnectionString = adminDBConString;
con.Open();
SqlCommand command = new SqlCommand();
command.Connection = con;
command.CommandText = "select CS_Case_ID, Case_Name from CS_Case where CS_Product_Type_ID = 2 and CS_Case_Status_ID = 1";
SqlDataReader thisReader = command.ExecuteReader();
int idxID = thisReader.GetOrdinal("CS_Case_ID");
int idxName = thisReader.GetOrdinal("Case_Name");
while (thisReader.Read())
{
bool found = false;
foreach (Cases tempCase in caseList)
{
if (tempCase.adminID == Int32.Parse(thisReader.GetValue(idxID).ToString()))
{
tempCase.alreadyInDB = true;
found = true;
}//end if
}//end foreach
if (!found)
{
cases = new Cases();
cases.adminID = Int32.Parse(thisReader.GetValue(idxID).ToString());
cases.name = thisReader.GetValue(idxName).ToString();
cases.alreadyInDB = false;
cases.officeList = new List<Offices>();
caseList.Add(cases);
}//end if
}//end while
thisReader.Close();
return caseList;
}//end try
catch (Exception excep1)
{
errorMessage = "Cases could not be loaded from the Admin Console." + "\r\n" + "Error message: " + excep1.ToString();
return caseList;
}//end catch
}//end method
Complex querying like this is best handled by LINQ. If you had a common element on both Users and Cases, then this would be a job for a join. But in this case instead of a common element, they each contain lists, and you want to "join" on those where the two lists have a common element.
So, to start off, what's the condition for a particular case to be included for a user?
case.officeList.Any(caseOffice => user.officeList.Any(userOffice => caseOffice.ID == userOffice.ID))
i.e. Any office in the case's officeList is contained in the user's officeList.
Now we have that condition, we can use it in a LINQ Where clause to pull out all the desired case:
caseList.Where(case =>
case.officeList.Any(caseOffice => user.officeList.Any(userOffice => caseOffice.ID == userOffice.ID)))
That returns our collection of cases, so finally we just want to Select out the part of the case we need, the adminID.
caseList.Where(case =>
case.officeList.Any(caseOffice => user.officeList.Any(userOffice => caseOffice.ID == userOffice.ID)))
.Select(case => case.adminID);
So putting that all together:
foreach(Users users in userList)
{
users.caseAdminIDList = caseList.Where(case =>
case.officeList.Any(caseOffice => user.officeList.Any(userOffice => caseOffice.ID == userOffice.ID)))
.Select(case => case.adminID).ToList();
}
public Cases()
{
List<Offices> officeList = new List<Offices>();
ID = 0;
}//end constructor
This is making a new varaible called officeList. It will not use the officeList outside the constructor.
This needs to be changed to:
officeList = new List<Offices>();
This will properly initialize the class field.
You can implement your Offices class like this:
class Offices:IEquatable<Offices>
{
public int ID { get; set; }
public string name { get; set; }
public bool Equals(Offices other)
{
return this.ID.Equals(other.ID);
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
Then in your code to have the Users class have any Offices that match the Offices in the Cases class just do this;
usersVariable.CaseAdminIDList = usersVariable.OfficeList.Intersect(casesVariable.OfficeList).Select(o => o.ID).ToList();
Also in your users and cases class you need to initialize the list properly(you are using automatic properties and in ctor declaring and initializing a whole new variable),something
like this for example:
class Users
{
public Users()
{
officeList = new List<Offices>();
caseAdminIDList = new List<int>();
ID = 0;
}//end constructor
private List<Offices> officeList;
public List<Offices> OfficeList
{
get { return officeList; }
set { officeList = value; }
}
private List<int> caseAdminIDList;
public List<int> CaseAdminIDList
{
get { return caseAdminIDList; }
set { caseAdminIDList = value; }
}
//other members.....
}