Infinite Loop When Loading Object - c#

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.

Related

Is it a good idea to initialize the object(entity) by using IDataRecord in the constructor?

Or is it against some paradigm?
using System.Data;
//This is a read only entity
class Entity
{
public Entity(IDataRecord data)
{
ID = (int)data["ID"];
Name = data["Name"].ToString();
Value = data["Value"] is DBNull ? default(int?) : (int)data["Value"];
}
public int ID { get; }
public string Name { get; }
public int? Value { get; }
}
How I use:
//...
using(var conn = new SqlConnection(connectionString))
using(var cmd = new SqlCommand("SELECT [ID],[Name],[Value] FROM [Table]", conn))
{
conn.Open();
using(var reader = cmd.ExecuteReader())
return reader.Cast<IDataRecord>()
.Select(data => new Entity(data))
.ToArray();
}
In the case of ensuring that it can be used correctly and safely, is this a good idea?
Or can be improved better?

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.

Populate Model class from Data in Backend

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.

Return List of Objects from stored procedure return data C#

I have a method that is calling a stored procedure and returning a list of objects. I am not sure how to add the result from the stored procedure to the list of objects. I tried to use model. Add but anyway I am using it I am getting error. I have identify the place I need help inside the code.
This is my code:
public List<Models.Type> get_Type(string Type_Group)
{
string connectionName = getConnectionStr();
List<Models.Type> model = null;
string myConnection = System.Configuration.ConfigurationManager.ConnectionStrings[connectionName].ToString();
SqlDatabase db = new SqlDatabase(myConnection);
using (DbCommand command = db.GetStoredProcCommand("LA_Get_Type"))
{
db.AddInParameter(command, "Type_Group", DbType.String, Type_Group);
var result = db.ExecuteReader(command);
try
{
if (result.FieldCount == 0)
model = null;
else
{
while (result.Read())
{
model = new List<Models.Type>()
{
//This is the place I don't know I tried model.Add but not sure what
to have after.
This code is when I have returning just 1 object but I want to
return list of objects
typeID = Convert.ToInt32(result["typeID"].ToString()),
type_group = result["type_group"].ToString(),
type_value = result["type_value"].ToString(),
type_desc = result["type_desc"].ToString(),
type_sort = Convert.ToInt32(result["type_sort"].ToString())
};
}
}
}
catch (Exception ex)
{
}
result.Close();
return model;
}
}
and this is my object:
public class Type
{
public int typeID { get; set; }
public string type_group { get; set; }
public string type_value { get; set; }
public string type_desc { get; set; }
public int type_sort { get; set; }
}
Change
while (result.Read())
{
model = new List<Models.Type>()
{
//This is the place I don't know I tried model.Add but not sure what
to have after.
This code is when I have returning just 1 object but I want to
return list of objects
typeID = Convert.ToInt32(result["typeID"].ToString()),
type_group = result["type_group"].ToString(),
type_value = result["type_value"].ToString(),
type_desc = result["type_desc"].ToString(),
type_sort = Convert.ToInt32(result["type_sort"].ToString())
};
}
to something like this:
model = new List<Models.Type>();
while (result.Read())
{
Models.Type aModel = new Model(){
typeID = Convert.ToInt32(result["typeID"].ToString()),
type_group = result["type_group"].ToString(),
type_value = result["type_value"].ToString(),
type_desc = result["type_desc"].ToString(),
type_sort = Convert.ToInt32(result["type_sort"].ToString())
};
model.Add(aModel);
}
Note that I'm creating a new object for each result and then adding them one by one to the list.

The specified table does not exist[entity]

I have spent hours trying to figure out why my database cannot find the table I have found numerous examples and none have seemed to help. I have created a separate class to handle the database operations so I can use it on multiple pages.
Here is the code
[Table]
public class MatchItem
{
[Column(IsPrimaryKey = true, CanBeNull=false,IsDbGenerated=true)]
public int MatchID { get; set; }
[Column(CanBeNull = false)]
public string MatchNumber { get; set; }
[Column(CanBeNull = false)]
public string EventName { get; set; }
[Column(CanBeNull = false)]
public DateTime Time { get; set; }
[Column(CanBeNull = false)]
public string[] RedTeams { get; set; }
[Column(CanBeNull = false)]
public string[] BlueTeams { get; set; }
[Column(CanBeNull = false)]
public int RedFinal { get; set; }
[Column(CanBeNull = false)]
public int BlueFinal{ get; set; }
}
Here is the Data context
public class MatchDataContext:DataContext
{
public MatchDataContext(string connectionString) :
base(connectionString)
{
}
public Table<MatchItem> Matches
{
get
{
return this.GetTable<MatchItem>();
}
}
}
I made a class so I could use it on multiple pages
public class MatchDBManager
{
private static string connectionString = #"Data Source=isostore:/DB.sdf";
public MatchDBManager()
{
initialize();
}
public void initialize()
{
using (MatchDataContext Mchdb = new MatchDataContext(connectionString))
{
if (Mchdb.DatabaseExists())
{
Console.WriteLine("DB already exists");
}
else
{
Mchdb.CreateDatabase();
Console.WriteLine("DB created");
}
}
}
public void addMatchData(IList<MatchItem> data)
{
//this.clearData();
//initialize();
using (MatchDataContext Mchdb = new MatchDataContext(connectionString))
{
Mchdb.Matches.InsertAllOnSubmit(data);
Mchdb.SubmitChanges();
}
}
public IList<MatchItem> getTeamData(string teamNum)
{
IList<MatchItem> MatchList = null;
using (MatchDataContext Mchdb = new MatchDataContext(connectionString))
{
IQueryable<MatchItem> mchQuery = from mch in Mchdb.Matches where (mch.RedTeams[0] == teamNum || mch.RedTeams[1] == teamNum || mch.RedTeams[2] == teamNum || mch.BlueTeams[0] == teamNum || mch.BlueTeams[1] == teamNum || mch.BlueTeams[2] == teamNum) select mch;
MatchList = mchQuery.ToList();
}
return MatchList;
}
public IList<MatchItem> getEventData(string eventID)
{
IList<MatchItem> MatchList = null;
using (MatchDataContext Mchdb = new MatchDataContext(connectionString))
{
IQueryable<MatchItem> mchQuery = from mch in Mchdb.Matches where mch.Event == eventID select mch;
MatchList = mchQuery.ToList();
}
return MatchList;
}
private void clearData()
{
using (MatchDataContext Mchdb = new MatchDataContext(connectionString))
{
if (Mchdb.DatabaseExists())
{
Mchdb.DeleteDatabase();
}
}
}
}
I have the error The specified table does not exist[Match].
Added here is where I download
public IList<MatchItem> ParseXML(XmlReader reader)
{
//List<string> save = new List<string>();
List<MatchItem> MatchList= new List<MatchItem>();
XElement matchData;
matchData = XElement.Load(reader);
StringBuilder output = new StringBuilder();
int count = 0;
var matches = from item
in matchData.Elements("match")
select item;
foreach (XElement eachmatch in matches)
{
MatchItem mch = new MatchItem();
string Time = ((eachmatch.Element("pubdate").Value).ToString());
mch.EventName = ((eachmatch.Element("event").Value).ToString());
mch.MatchNumber = ((eachmatch.Element("mch").Value).ToString() + (eachmatch.Element("typ").Value).ToString());
string[] RT = { eachmatch.Element("red1").Value.ToString(), eachmatch.Element("red2").Value.ToString(), eachmatch.Element("red3").Value.ToString() };
string[] BT = { eachmatch.Element("blue1").Value.ToString(), eachmatch.Element("blue2").Value.ToString(), eachmatch.Element("blue3").Value.ToString() };
string RF = ((eachmatch.Element("rfin").Value).ToString());
string BF = ((eachmatch.Element("bfin").Value).ToString());
// Time = Time.Substring(0, (Time.IndexOf("+") - 1));
mch.Time = DateTime.Parse(Time);
mch.RedTeams = RT;
mch.BlueTeams = BT;
mch.RedFinal = int.Parse(RF);
mch.BlueFinal= int.Parse(BF);
mch.MatchID = count;
count += 1;
MatchList.Add(mch);
}
return MatchList;
}
This is where I call this method
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
initializeDB();
if (e.Error == null)
{
XmlReader reader = XmlReader.Create(new StringReader(e.Result));
DownloadInfo di = new DownloadInfo();
IList <MatchItem>data= di.ParseXML(reader);
outputer(data);
addData(data.ToList<MatchItem>());
}
else
{
IList<MatchItem> data = getTeamData(strMyTeam);
outputer(data);
}
}
I ended up removing the DatabaseManager class and putting the functions in the main code
Then I output them to the screen here
public void outputer(IList<MatchItem> mch)
{
for (int i = 0; i < mch.Count; i++)
{
Score sc = new Score();
sc.Width = lsbData.Width;
sc.Height = sc.Height;
sc.Time = mch[i].Time.ToString();
sc.Event = mch[i].EventName;
sc.RT = mch[i].RedTeams[0] + " " + mch[i].RedTeams[1] + " " + mch[i].RedTeams[2];
sc.BT = mch[i].BlueTeams[0] + " " + mch[i].BlueTeams[1] + " " + mch[i].BlueTeams[2];
sc.RF = mch[i].RedFinal.ToString();
sc.BF = mch[i].BlueFinal.ToString();
lsbData.Items.Add(sc);
}
}
*note:score is a custom control that works(and worked) before the database code *
I don't see where you actually create a Match Object.
if you have you need to include that code in the question as well. and if you haven't, that would explain why it doesn't exist.
Addition
in order to add Match Objects to a list you will have to create the objects first then add them to the list, I don't think you can create the whole list of objects before creating each individual object.
more Additional Information
the object still needs to be created before you can add items to it. that is what the error is telling you. you don't have the object to insert data into.
Match Table1 = new Match();
this creates a new Match object which allows you to access the pieces of the object and insert data into the object like this
Table1.MatchNumber = 42
you can't add to something to a memory location until you set aside that memory location for that specific person and give it a name.
when you create that class you can add functions and all sorts of fun stuff to it, but you can't use any of it until you have created a Match Object.
you can't add something to a list that doesn't exist, you have to create the Match Object first, then add it to the list

Categories

Resources