I am developing a C# Android that takes the user input and add it to a database, then in another Activity, it displays the user input with an option to edit his input again.
So I have 2 activities and 1 public class which links them together. I am using SQLLite to save the user input into a database (in the MainActivity.cs) then (in the secondActivity) it retrieves the saved value (which is the user input) from the public database (located in the public class called Class1) and displays it in a Textview.
Class1.cs
namespace App
{
public class Class1
{
public static string dpPath= Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "user.db3");
public void Insert(string Quantity, string name)
{
var db = new SQLiteConnection(Class1.dpPath);
var logintable = new LoginTable();
logintable.quantity = Quantity;
logintable.name = name;
db.Insert(logintable);
}
public void edit(string Quantity)
{
var db = new SQLiteConnection(Class1.dpPath);
var logintable = new LoginTable();
logintable.quantity = Quantity;
db.Update(logintable);
}
public void delete(int id)
{
var db = new SQLiteConnection(Class1.dpPath);
var logintable = new LoginTable();
logintable.id = id;
db.Delete(logintable);
}
public Class1()
{
//Creating database, if it doesn't already exist
if (!File.Exists(Class1.dpPath))
{
var db = new SQLiteConnection(Class1.dpPath);
db.CreateTable<LoginTable>();
}
}
public class LoginTable
{
[PrimaryKey, AutoIncrement, Column("_Id")]
public int id { get; set; } // AutoIncrement and set primarykey
[MaxLength(25)]
public string quantity { get; set; }
[MaxLength(15)]
public string name { get; set; }
public LoginTable()
{
}
}
}
MainActivity.cs
Class1 cl = new Class1():
cl.Insert(input.ToString(), name.ToLower());
SecondActivity.cs
Class1 cl = new Class1():
cl.edit(input.ToString());
var db = new SQLiteConnection(Class1.dpPath);
var table = db.Table<Class1.LoginTable>();
foreach( var item in table) {
textView.Text += item.name + " " + item.quantity;
}
Well, I am getting in the textview in the secondActivity, the input that was entered the first time (in the MainActivity) and not the one which was edited later in the SecondActivity. I thought that maybe because I have created two different instance of the Class1 and each one is working with a different Table. In addition, I have tried to initialise a public static instance of the Class1 inside the Class1 itself like that:
public class Class1 { public static Class1 cl = new Class1(); }
but did not work either, the textview is still displaying the original input and not the edited one. I need to be able to edit the database from each activitie.. Please help me to find a solution.
UPDATE
I have created a new class Class2 and I have initialise inside it a new instance of Class1 like that:
public class Class2
{
public static Class1 cl = new Class1();
}
And then i have tried to access this instance of the class1 in the Main and second activity, so in the Main my code are:
Class2.cl.Insert(input.ToString(), name.ToLower());
and in the second activity my code are:
Class2.cl.edit(input.ToString());
var db = new SQLiteConnection(Class1.dpPath);
var table = db.Table<Class1.LoginTable>(); // The issue is in this line
foreach( var item in table) {
textView.Text += item.name + " " + item.quantity;
}
So now the issue I think is in the secondActivity, the var table is getting only one table which is the one where the original input in the Main activity is stored, and when updating the value in the second activity, it is not considering the second table which stores the edited input. But still i don't know how to solve this.
On first glance it appears that your Edit() method is not correct. You have this:
public void edit(string Quantity)
{
var db = new SQLiteConnection(Class1.dpPath);
var logintable = new LoginTable();
logintable.quantity = Quantity;
db.Update(logintable);
}
But you have no way to quantify which item in the database that you wish to update; you're just creating a new item and then trying to update the table with that item. You should instead add some way to select the item you wish to edit, like this example:
public void Edit(int id, string quantity)
{
var db = new SQLiteConnection(Class1.dbPath);
var table = db.Table<Class1.LoginTable>();
var itemToEdit = table.First(f=>f.Id == id);
itemToEdit.quantity = quantity;
db.Update(itemToEdit);
}
As you can see, the example above gets the item from your table, edits the item and updates your table with the data you gave it.
Your Delete() method should follow a similar path, instead of instantiating a new object just to delete it.
Also about your update, you should consider researching the Singleton pattern. Here is a better way to handle a static object than your Class1/Class2 implementation. In your Class1 class, add the field:
public static readonly Class1 Instance = new Class1()
And then reference your instance from other classes like this:
Class1.Instance.Insert(input.ToString(), name.ToLower());
This way there is no way for you to accidentally create a new instance of Class1.
Related
A quick question on OOP. I am using a list together with a class and class constructor. So I use the class constructor to define the data set and then add each record to my list as the user creates them.
My questions is once the data is in the list and say I want to alter something is it good practice to find the record, create an instance using that record and then use my class methods to do whatever needs doing - and then put it back in the list?
For example below I have my class with constructor. Lets say I only want the system to release strCode if the Privacy field is set to public. Now just using Instances I would use for example Console.WriteLine(whateverproduct.ProductCode) but if the record is already in a list do i take it out of the list - create an instance and then use this method?
class Product
{
private String strCode;
private Double dblCost;
private Double dblNet;
private String strPrivacy;
public Product(String _strCode, Double _dblCost, Double _dblNet, String _strPrivacy)
{
strCode = _strCode;
dblCost = _dblCost;
dblNet = _dblNet;
strPrivacy = _strPrivacy;
}
public string ProductCode
{
get
{
if (strPrivacy == "Public")
{
return strCode;
}
else
{
return "Product Private Can't release code";
}
}
}
Lets say we have the following:
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
private string _test = "Some constant value at this point";
public string GetTest()
{
return _test;
}
public void SetTest()
{
//Nothing happens, you aren't allow to alter it.
//_test = "some constant 2";
}
}
public class Program
{
public static void Main(string[] args)
{
List<Test> listOfTest = new List<Test>()
{
new Test() {Id = 0, Name = "NumberOne", Amount = 1.0M},
new Test() {Id = 1, Name = "NumberTwo", Amount = 2.0M}
};
Test target = listOfTest.First(x => x.Id == 0);
Console.WriteLine(target.Name);
target.Name = "NumberOneUpdated";
Console.WriteLine(listOfTest.First(x => x.Id == 0).Name);
Console.WriteLine(listOfTest.First(x => x.Id == 0).GetTest());//This will alsways be "Some constant value at this point";
Console.ReadLine();
}
}
Technically you could do away with the SetTest method entirely. However, I included it to demonstrate, what it would look like, if you wanted to alter _test.
You don't want to ever create a new instance of a class, you already have an instance of. you can just alter the class where it is allowed by the author of the class, where you need to. And keep that class reference for as long as you need it. Once you are done, the reference will be garbage collected, once the program finds no active reference to your object(instance).
I'm going to chunk this down to as simple a case as I can, but this happens for everything.
I'm basing most of my data model POCO objects on a BaseDataObject defined as follows:
public class BaseDataObject
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
My code-first data model has a Client object:
public class Client : BaseDataObject
{
public string Name { get; set; }
public virtual Category Category { get; set; }
public virtual Category Subcategory { get; set; }
}
The Category object is pretty simple:
public class Category : BaseDataObject
{
public string Name { get; set; }
}
The required Id property exists in the inherited BaseDataObject.
To add entities, I'm using the following repo:
public class DataRepository<TModel, TContext>
where TModel : BaseDataObject
where TContext : DbContext
{
public int AddItem(T item)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
// These are important as well.
public List<T> ListItems(int pageNumber = 0)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
// Deleted property is also included in BaseDataObject.
return db.Set<T>().Where(x => !x.Deleted).OrderBy(x => x.Id).Skip(10 * pageNumber).ToList();
}
public T GetSingleItem(int id)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
return db.Set<T>().SingleOrDefault(x => x.Id == id && !x.Deleted);
}
}
}
This adds a new client perfectly fine, but there's something weird about my data model here that's causing Entity Framework to also add 2 new Categories every time I add a client based on which categories I'm selecting on my form.
Here's my form's code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
BindDropDownList<Category>(CategoryList);
BindDropDownList<Category>(SubcategoryList);
}
// Error handling things
}
}
private void BindDropDownList<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
control.DataSource = repo.ListItems();
control.DataTextField = "Name";
control.DataValueField = "Id";
control.DataBind();
control.Items.Insert(0, new ListItem("-- Please select --", "0"));
}
private TModel GetDropDownListSelection<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
int.TryParse(control.SelectedItem.Value, out int selectedItemId);
return repo.GetSingleItem(selectedItemId);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
Category = selectedCategory,
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
Unless there's something wrong with the way I'm creating the relationship here (using the virtual keyword or something maybe) then I can't see any reason why this would add new Categories to the database as duplicates of existing ones based on the selections I make in the drop down lists.
Why is this happening? What have I got wrong here?
The DbSet<T>.Add method cascades recursively to navigation properties which are not currently tracked by the context and marks them as Added. So when you do
db.Set<T>().Add(item);
it actually marks both Client class referenced Category entities as Added, hence SaveChanges inserts two new duplicate Category records.
The usual solution is to tell EF that entities are existing by attaching them to the context in advance. For instance, if you replace repo.AddItem(client); with
using (var db = new ApplicationDbContext())
{
if (client.Category != null) db.Set<Category>().Attach(client.Category);
if (client.Subcategory != null) db.Set<Category>().Attach(client.Subcategory);
db.Set<Client>().Add(item);
db.SaveChanges();
}
everything will be fine.
The problem is that you use generic repository implementation which does not provide you the necessary control. But that's your design decision issue, not EF. The above is EF intended way to handle such operation. How you can fit it into your design is up to you (I personally would eliminate the generic repository anti-pattern and use directly the db context).
It is really hard to judge from your listing because no FK mappings are included nor the base model details are provided.
However, it would appear that the Category that you assigned to client does not have PK set, and (most likely) only has the Name set, and you have no unique IX on that.
So EF has no reasonable way to work out that this is the right category.
One way to sort it is
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext>();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
// either
Category = new DataRepository<Category , ApplicationDbContext>().GetSingleItem(selectedCategory.id),
// or, easier (assuming you have FK properties defined on the model)
CategoryId = selectedCategory.Id,
// repeat as needed
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
I would like to point to a row.
Get the Oid(the parameter I want to pass).
When I click a button on the ribbon. It will open MifarePasswordForm. I would like to pass Oid to MifarePasswordForm so that the Oid can be saved in Mifare Card but I'm stuck at getting the Oid. So far, this is what I've got.
public void barButtonItem1_ItemClick()
{
staff entity = gridView.GetRow(gridView.GetSelectedRows()[0]) as staff;
entity.Oid;
MifarePasswordForm modalForm = new MifarePasswordForm();
modalForm.ShowDialog();
RefreshData();
}
This is my password form.
public MifarePasswordForm(int _iD)
{
InitializeComponent();
int iD = _iD;
}
Updated code
public void barButtonItem1_ItemClick()
{
staff entity = gridView.GetRow(gridView.GetSelectedRows()[0]) as staff;
MifarePasswordForm modalForm = new MifarePasswordForm(entity.Oid);
modalForm.ShowDialog();
RefreshData();
}
public MifarePasswordForm(int _iD)
{
InitializeComponent();
int iD = _iD;
textBox1.Text += iD;
}
What you can do is, pass your parameter to form in the constructor itself OR, make a public property and access it after creating formInstance and assign it your designated value.
e.g.
MifarePasswordForm modalForm = new MifarePasswordForm(entity.Oid);
modalForm.ShowDialog();
Evening all,
To the point: Within my WPF application I would like to display data from an access database within a listbox in the format of a ToString method created within another class. -- I can display the data, but it does not contain formatting.
Context of my question:
I am creating an application for my graded unit at college which adds, deletes and displays data from an access database. I am having no trouble with adding or deleting data to the database, however, I am struggling to display the data in a particular format.
Due to specific requirements, I have had to create an abstract Games class, with the subclasses Platform and Mobile (games).
I would like to know how to display data from an access database in a listbox (though this is flexible to change), whilst formatting the content to a previously created ToString() method in both the Platform and Mobile class. I understand that I may have to create two separate methods to display platform and mobile games, as they each have an additional variable.
Currently, I am storing my listPlatform() method within my Catalogue class, which is accessed from a separate window (EmployeeWindow, which contains the list view box, then accessing this method and calling it via a button_click event.
Catalogue class --
public List<string> listPlatform()
{
List<string> data = new List<string>();
string queryString = "SELECT ID, Game_Name, Developer, Publisher, Genre, Age_Rating, Price, Quantity, Description, Platform FROM GameDetails";
using (OleDbConnection connection = new OleDbConnection(ConnString))
{
OleDbCommand command = new OleDbCommand(queryString, connection);
connection.Open();
OleDbDataReader reader = command.ExecuteReader();
while (reader.Read())
{
int id = reader.GetInt32(0);
string gName = reader.GetString(1);
string gDeveloper = reader.GetString(2);
string gPublisher = reader.GetString(3);
string gGenre = reader.GetString(4);
int gAgeRating = reader.GetInt32(5);
var gPrice = reader.GetValue(6);
var gQuantity = reader.GetValue(7);
var gDescription = reader.GetValue(8);
var gPlatform = reader.GetValue(9);
data.Add(id + gName + gDeveloper + gPublisher + gGenre + gAgeRating + gPrice
+ gQuantity + gDescription + gPlatform);
}
reader.Close();
}
return data;
}
EmployeeWindow --
private void btnDisplay_Click(object sender, RoutedEventArgs e)
{
List<string> data = theCatalogue.listPlatform();
lstvwGames.Items.Clear();
foreach (string s in data)
{
lstvwGames.Items.Add(s);
}
}
Platform class --
/// <summary>
/// Returns a string representation of a Platform game
/// </summary>
/// <returns></returns>
public override string ToString()
{
string strout = string.Format(base.ToString() + "Platform:{0}", platform);
return strout;
}
I hope that my question makes sense and that I have provided enough information to give you some understanding of what it is that I am trying to do.
I think that, in this scenario, you need to add another class to your code.
The Game class that you could model looking at the fields present in your database table
public class Game
{
public int ID {get;set;}
public string GameName {get;set;}
public string Developer {get;set;}
public string Publisher {get;set;}
public string Genre {get;set;}
public string Age_Rating {get;set;}
public decimal Price {get;set;}
public int Quantity {get;set;}
public string Description {get;set;}
public string Platform {get;set;}
public override string ToString()
{
return this.GameName + " - " + this.Genre;
}
}
Now in the Catalogue class, when you read your database you build a List<Game> not a List<String>
public List<Game> listPlatform()
{
.....
List<Game> games = new List<Game>();
while (reader.Read())
{
Game g = new Game();
g.ID = reader.GetInt32(0);
g.GameName = reader.GetString(1);
... and so on for the rest of fields
games.Add(g);
}
...
return games;
}
Finally, when you need to add the games to your ListBox, you could write
private void btnDisplay_Click(object sender, RoutedEventArgs e)
{
List<Game> data = theCatalogue.listPlatform();
lstvwGames.Items.Clear();
foreach (Game g in data)
{
lstvwGames.Items.Add(g.ToString());
}
}
And you have a list filled with GameName and Genre.
EDIT to complete this answer,
Finally, you could directly set the DataSource, DisplayMember and ValueMember properties of the ListBox with your List<Game> and some of its properties removing the need to have a loop to fill items
private void btnDisplay_Click(object sender, RoutedEventArgs e)
{
lstvwGames.ValueMember = "ID";
lstvwGames.DisplayMember = "GameData";
lstvwGames.DataSource = theCatalogue.listPlatform();
}
In this example the DisplayMember property is assigned to a new GameData field that you should define inside your Game class. This new readonly property could be the actual return value of the ToString method of the same class or another value of your choice
public class Game
{
.....
public string GameData
{
// Only a getter, thus readonly
get
{
return this.ToString();
}
}
}
Of course you could change the ToString method or the GameData property inside the Game class to return the info you really want to display in the listbox.
I do not see you using the tostring override method. Maybe you meant to use it here?
lstvwGames.Items.Add(s.ToString());
I have an object which has some properties and a few of those properties are Lists. Each list contains instances of other classes. What i want to do is take the first item from a list and overwrite those property values.
Here's a pseudo example of what i have:
public class User
{
public List<Address> Addresses = new List<Address>();
public User ( )
{
Addresses = fill with data;
}
}
public class TestUser
{
public User user; // Is filled somewhere in this class
public void TestUpdateList ( Address addr )
{
// The param "addr" contains new values
// These values must ALWAYS be placed in the first item
// of the "Addresses" list.
// Get the first Address object and overwrite that with
// the new "addr" object
user.Addresses[0] = addr; // <-- doesn't work, but should give you an idea
}
}
I hope this example shed some light on what i want to do.
So i am basically looking for a way to "update" an existing item in a list, which is in this case an object.
It is not entirely clear what you are trying to accomplish, however, see the following code -- there is an Address, a User, and an utility called FeatureX that replaces the first Address of a User with a given value.
class Address {
public string Street { get; set; }
}
class User {
public List<Address> Addresses = new List<Address>();
}
class FeatureX {
public void UpdateUserWithAddress(User user, Address address) {
if (user.Addresses.Count > 0) {
user.Addresses[0] = address;
} else {
user.Addresses.Add(address);
}
}
}
The following usage outputs 'Xyz' two times:
User o = new User();
Address a = new Address() { Street = "Xyz" };
new FeatureX().UpdateUserWithAddress(o, a);
Console.WriteLine(o.Addresses[0].Street);
o = new User();
o.Addresses.Add(new Address { Street = "jjj" });
new FeatureX().UpdateUserWithAddress(o, a);
Console.WriteLine(o.Addresses[0].Street);
Be aware that public fields may cause a lot of trouble if you share your DLL with a third party.
Your example doesn't compile because you're accessing the Addresses property via class name. That is only possible if it is static. So you need an instance of a user first, to update his addresses:
User u = new User(userID); // assuming that theres a constructor that takes an identifier
u.Addresses[0] = addr;
C# Language Specification: 10.2.5 Static and instance members
I think the problem is that Addresses is a private field.
This works:
[TestFixture]
public class ListTest
{
[Test]
public void UpdateTest()
{
var user = new User();
user.Addresses.Add(new Address{Name = "Johan"});
user.Addresses[0] = new Address { Name = "w00" };
}
}
public class User
{
public List<Address> Addresses { get;private set; }
public User()
{
Addresses= new List<Address>();
}
}
public class Address
{
public string Name { get; set; }
}
public void TestUpdateList ( User user, Address addr )
{
// The param "addr" contains new values
// These values must ALWAYS be placed in the first item
// of the "Addresses" list.
// Get the first Address object and overwrite that with
// the new "addr" object
user.Addresses[0] = addr; // <-- doesn't work, but should give you an idea
}