Create string array from SQLite database column in Android Studio - arrays

I have created an app with an SQLite database and a ListView that I want to populate using the database. To do this I have written an adapter class called HomeListAdapter. This class takes 4 string arrays as input and that is where my problem is. At first, I just used random string arrays to populate the ListView by typing them myself, for example:
String[] homelist_name_short = {
"Flower", "Bush", "Tree"};
String[] homelist_name_long = {
"Red rose", "Berry bush", "Oak"};
String[] homelist_date = {
"20-9-2017", "11-10-2017", "12-10-2017"};
String[] homelist_price = {
"€1.50", "€2.48", "€0.68"};
Now I want this to be put into the listview automatically and to do that I have written the code that can be seen below..
I have created a class called Home:
public class Home {
private String mShortHomeName;
private String mLongHomeName;
private String mHomeDate;
private String mHomePrice;
public Home(String ShortName, String LongName, String Date, String Price) {
this.mShortHomeName = ShortName;
this.mLongHomeName = LongName;
this.mHomeDate = Date;
this.mHomePrice = Price;
}
public String getShortName() {
return this.mShortHomeName;
}
public String getLongName() {
return this.mLongHomeName;
}
public String getDate() {
return this.mHomeDate;
}
public String getPrice() {
return this.mHomePrice;
}
}
Added the following to my DatabaseHelper:
public Cursor getAllHomesAsCursor() {
SQLiteDatabase db = this.getWritableDatabase();
String[] columns = {"rowid as _id","*"};
return db.query(TABLE_NAME,columns,null,null,null,null,null);
}
The following to the activity that contains my listview:
DatabaseHelper db = new DatabaseHelper(getActivity());
Cursor csr = db.getAllHomesAsCursor();
HLAdapter adapter = new HLAdapter(getActivity(), csr);
listView.setAdapter(adapter);
The HLAdapter looks as follows:
public class HLAdapter extends CursorAdapter {
public HLAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
}
#Override
public View newView(Context context, Cursor csr, ViewGroup parent) {
return LayoutInflater.from(context).inflate(
R.layout.homelist_listview_layout,
parent,
false
);
}
#Override
public void bindView(View view, Context context, Cursor csr) {
TextView sname = (TextView) view.findViewById(R.id.homelist_name_short);
TextView lname = (TextView) view.findViewById(R.id.homelist_name_long);
TextView date = (TextView) view.findViewById(R.id.homelist_date);
TextView price = (TextView) view.findViewById(R.id.homelist_price);
sname.setText(csr.getString(csr.getColumnIndex("name_short")));
lname.setText(csr.getString(csr.getColumnIndex("name_long")));
date.setText(csr.getString(csr.getColumnIndex("date")));
price.setText(csr.getString(csr.getColumnIndex("price")));
}
}
My DatabaseHelper class looks as follows:
public class DatabaseHelper extends SQLiteOpenHelper{
public static final String DATABASE_NAME = "Main.db";
public static final String TABLE_NAME = "current_table";
public static final String COL_1 = "name_short";
public static final String COL_2 = "name_long";
public static final String COL_3 = "date";
public static final String COL_4 = "price";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, 1);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + TABLE_NAME + " (name_short TEXT,name_long TEXT, due_date TEXT, price TEXT) ");
}
#Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public Cursor getAllHomesAsCursor() {
SQLiteDatabase db = this.getWritableDatabase();
String[] columns = {"rowid as _id","*"};
return db.query(TABLE_NAME,columns,null,null,null,null,null);
}
public boolean insertData(String name_short, String name_long, String due_date, String price) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(COL_1,name_short);
contentValues.put(COL_2,name_long);
contentValues.put(COL_3,due_date);
contentValues.put(COL_4,price);
long result = db.insert(TABLE_NAME,null,contentValues);
if(result == -1)
return false;
else
return true;
}
public void deleteAllData() {
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DELETE FROM " + TABLE_NAME);
}
public Cursor getAllData() {
SQLiteDatabase db = this.getWritableDatabase();
Cursor res = db.rawQuery("SELECT * FROM " + TABLE_NAME ,null);
return res;
}
public Cursor getSpecifiedColumnData(String column) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor res = db.rawQuery("SELECT column FROM " + TABLE_NAME,null);
return res;
}
}
I would think that my code should be working fine, but when I run the app. It closes immediately. Can you tell me what the problem is?

I believe the issue is that you need 4 String arrays to be passed to the adapter, so doing it like above you'd need to have a unique equivalent of getAllData for each type.
However considering that a house has a short name, long name, date and price a better approach could to consider all of these properties as an object and thus create a class. You could then create a List not of String objects but as a List of House objects, you'd be able to get them all in one go etc.
So (P.S. for the sake of my sanity I've incorporated the SO4522191 into the following so I can keep some sort of track of the code) :-
1) Create your Home Object to hold all values/properties of a house:-
1-a) Create a file the same as your home name, it's going to be a java class file.
In this case I've called it SO45422191Home, the code could be along the lines of :-
public class SO45422191Home {
private String mShortHomeName;
private String mLongHomeName;
private String mHomeDate;
private String mHomePrice;
public SO45422191Home(String ShortName, String LongName, String Date, String Price) {
this.mShortHomeName = ShortName;
this.mLongHomeName = LongName;
this.mHomeDate = Date;
this.mHomePrice = Price;
}
public String getShortName() {
return this.mShortHomeName;
}
public String getLongName() {
return this.mLongHomeName;
}
public String getDate() {
return this.mHomeDate;
}
public String getPrice() {
return this.mHomePrice;
}
}
Explanation
Using the above we can create a SO45422191Home object, in code elsewhere e.g. in your activity, by using something
like SO45422191Home Myhome = new SO45422191Home("Flower","Red
Rose","20-9-2017","1.50");.
With the MyHome object you can extract the properties e.g.
MyHome.getPrice() would return a String with a value of 1.50.
Similar for the other properties.
You can create an array of objects e.g. SO45422191Home[] homes = new
SO45422191Home[3]; will create an array of 3 (empty) SO45422191Home
objects. We could set the first element of the array using homes[0] =
new SO45422191Home("Bush","Cherry","11-10-2017","2.48");
2) Create a means of getting an array of SO45422191Home objects from the database.
here's some code for this:-
public List<SO45422191Home> getAllHomes() {
List<SO45422191Home> rv = new ArrayList<>();
SQLiteDatabase db = this.getWritableDatabase();
Cursor csr = db.query(HOMETABLE,null,null,null,null,null,null);
while (csr.moveToNext()) {
SO45422191Home h = new SO45422191Home(
csr.getString(csr.getColumnIndex(SHORTHOMENAME)),
csr.getString(csr.getColumnIndex(LONGHOMENAME)),
csr.getString(csr.getColumnIndex(HOMEDATE)),
csr.getString(csr.getColumnIndex(HOMEPRICE))
);
rv.add(h);
}
csr.close();
return rv;
}
Explanation You used List and added elements to the list, List is similar but for SO45422191Home objects rather
than String objects.
The Database is opened, if not already open, using SQLiteDatabase db
= this.getWritableDatabase();.
All rows are extracted into a cursor.
The cursor is traversed each row at a time.
For each row a SO45422191Home object is created by getting the respective data from the cursor
(Note that csr.getColumnIndex(columnname) is used
rather than hard coding the column's index/offset, doing so can reduce
the chance for errors and also reduce overheads should changes be
applied.).
The the new object is added to the list.
Obviously column names would have to be adjusted, you may also want to add db.close() before the return.
3) Amend your adapter to take and use the the single list of objects rather than the 4 List.
here's an example adapter for use by a List (Note that R.layout.homeentry is the layout used for each entry in the ListView), the layout is below in the section re Cursor Adapter:-
public class AdapterHomeList2 extends ArrayAdapter {
List<SO45422191Home> homes;
LayoutInflater lInflater;
public AdapterHomeList2(Context context, List<SO45422191Home> homes) {
super(context,R.layout.homeentry, homes);
lInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.homes = homes;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View view = convertView;
if (view == null) {
view = lInflater.inflate(R.layout.homeentry, parent, false);
}
TextView sname = (TextView) view.findViewById(R.id.shortname);
TextView lname = (TextView) view.findViewById(R.id.longname);
TextView date = (TextView) view.findViewById(R.id.date);
TextView price = (TextView) view.findViewById(R.id.price);
sname.setText(homes.get(position).getShortName());
lname.setText(homes.get(position).getLongName());
date.setText(homes.get(position).getDate());
price.setText(homes.get(position).getPrice());
return view;
}
}
This is the code for an ArrayList :-
public class AdapterHomeList3 extends ArrayAdapter {
ArrayList<SO45422191Home> homes;
LayoutInflater lInflater;
public AdapterHomeList3(Context context, ArrayList<SO45422191Home> homes) {
super(context,R.layout.homeentry, homes);
lInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.homes = homes;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View view = convertView;
if (view == null) {
view = lInflater.inflate(R.layout.homeentry, parent, false);
}
TextView sname = (TextView) view.findViewById(R.id.shortname);
TextView lname = (TextView) view.findViewById(R.id.longname);
TextView date = (TextView) view.findViewById(R.id.date);
TextView price = (TextView) view.findViewById(R.id.price);
sname.setText(homes.get(position).getShortName());
lname.setText(homes.get(position).getLongName());
date.setText(homes.get(position).getDate());
price.setText(homes.get(position).getPrice());
return view;
}
}
Using a CursorAdapter
1) Add a new method to extract a cursor with all rows (NOTE! for cursor adapter a row named _id is required)
public Cursor getAllHomesAsCursor() {
SQLiteDatabase db = this.getWritableDatabase();
String[] columns = {"rowid as _id","*"};
return db.query(HOMETABLE,columns,null,null,null,null,null);
}
Note! instead of all columns i.e. coding null as the second parameter
to 'query' (which doesn't get the hidden rowid column (assuming
WITHOUT ROWID hasn't been used)), will will get the rowid (a unqiue
row identifier) and name this AS _id to suite the Cursor Adapter
hence "rowid as _id", the following * as the second element of
the columns array means all rows (specifying null as 2nd parameter
result in SELECT * .....).
Otherwise it's pretty simple. NOTE you must not close the database,
otherwise you can't access the cursor.
2) You will need a layout for each item in the list, as you would for a custom array adapter and it can be the same one i.e. there are no diferences according to which adapter is used. e.g. I created :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/shortname"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/longname"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/date"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/price"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
3) Create The Cursor Adpater as a class file, very similar to an Array Adapter.
public class AdapterHomeList extends CursorAdapter {
public AdapterHomeList(Context context, Cursor cursor) {
super(context, cursor,0);
}
#Override
public View newView(Context context, Cursor csr, ViewGroup parent) {
return LayoutInflater.from(context).inflate(
R.layout.homeentry, //<< layout for each list item
parent,
false
);
}
#Override
public void bindView(View view, Context context, Cursor csr) {
TextView sname = (TextView) view.findViewById(R.id.shortname);
TextView lname = (TextView) view.findViewById(R.id.longname);
TextView date = (TextView) view.findViewById(R.id.date);
TextView price = (TextView) view.findViewById(R.id.price);
sname.setText(csr.getString(csr.getColumnIndex(SO45422191.SHORTHOMENAME)));
lname.setText(csr.getString(csr.getColumnIndex(SO45422191.LONGHOMENAME)));
date.setText(csr.getString(csr.getColumnIndex(SO45422191.HOMEDATE)));
price.setText(csr.getString(csr.getColumnIndex(SO45422191.HOMEPRICE)));
}
}
Note! R.layout.homeentry being the layout for the list entries
and id's are from this.
4) From the respective activity, get the cursor, get an instance of the adapter and set the ListView to use the adapter.
e.g.:-
Cursor csr = dbhlp.getAllHomesAsCursor();
AdapterHomeList ahl = new AdapterHomeList(this,csr);
ListView hl = (ListView) this.findViewById(R.id.homelist);
hl.setAdapter(ahl);
result :-
Using cursor.getColumnIndex()
getColumnIndex
int getColumnIndex (String columnName)
Returns the zero-based index for the given column name, or -1 if the
column doesn't exist. If you expect the column to exist use
getColumnIndexOrThrow(String) instead, which will make the error more
clear.
SQLiteCursor
Using getColumnIndex as opposed to specifying the index removes the need to manually determine column offsets.
For example purposes, there is a table, named items with 3 columns, named as name, date and price :-
Using a query that uses the equivalent of SELECT * FROM items, the query will return a cursor with 3 columns, name, date and price
column name would have an offset of 0.
column date would have an offset of 1.
column price would have an offset of 2.
To extract the date from the cursor you could code cursor.getString(1);
However, if you were to have a query based upon SELECT date, price, name FROM items then the offsets would be:-
column name would have an offset of 2.
column date would have an offset of 0.
column price would have an offset of 1.
In this case you'd have to code cursor.getString(0); to extract the date.
It could be easy to inadvertently code the wrong offset, especially when using larger tables or when joining tables or when introducing generated columns.
Using cursor.getString(cursor.getColumnIndex("date")); could be used in both situations as it would return 1 in the first case and 0 in the second case.
Above, as an example, sname.setText(csr.getString(csr.getColumnIndex(SO45422191.SHORTHOMENAME))); has been coded.
Home.SHORTHOMENAME is a class varaible defined in the Database Helper (i.e. SO45422191 is the DatabaseHelper Class and SHORTHOMENAME is the class variable) that equates to the column name of the respective column as can be seen from the Database Helper code extract:-
public class SO45422191 extends SQLiteOpenHelper {
public static final String DBNAME = "SO45422191";
public static final String HOMETABLE = "homes";
public static final String SHORTHOMENAME = "shorthomename";
public static final String LONGHOMENAME = "longhomename";
public static final String HOMEDATE = "homedate";
public static final String HOMEPRICE = "homeprice";
// constructor
public SO45422191(Context context) {
super(context, DBNAME , null , 1);
}
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table " + HOMETABLE +
"(" +
SHORTHOMENAME + " TEXT, " +
LONGHOMENAME + " TEXT, " +
HOMEDATE + " TEXT, " +
HOMEPRICE + ")");

Related

Make cursor corresponding array with multiple rows followed by same row id's

I have a DB in which id's are change after few rows, then same I'd in some rows then change I'd. What I want that, the same I'd values will be populated listview's in one line, then when the Ids are same, they are all in one row. And thus the next rows populate with same rows which have same ID's.
My DB columns and rows are as follows:
Verse_id -------words_id----------words_ar------ translate_bn--
1-----------------------1---------------Mamun-----------Assistant---
1-----------------------2--------------- Salam------------Operator------
1-----------------------3---------------John--------------Assistant------
2 ----------------------1--------------- Smith-------------Manager------
2-----------------------2---------------Roger--------------Director--------
3-----------------------1---------------Qoel---------------Helper----------
3-----------------------2---------------Saki---------------Mechanics-----
3-----------------------3----------------Ali-----------------Getman----------
I want this DB in three lines, as follows : (listview):
1. Mamun. Salam John
Assistant. Operator. Assistant
------------------------------------------------------------------
2. Smith. Roger.
Manager. Director
------------------------------------------------------------------
3. Qoel. Saki. Ali.
Helper. Mechanics. Getman
I have Tried in Two way :
First :
private static final String PRIMARY_ID = "_id";
private static final String TABLE_NAME = "bywords";
private static final String FRIEND_ID = "verse_id";
private static final String WORDS_ID = "words_id";
private static final String WORDS_bN = "translate_bn";
private static final String WORDS_AR = "words_ar";
private SQLiteDatabase database;
private ArrayList<String> friends;
private ArrayList<String> trans1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
mContext = this;
ExternalDbOpenHelper dbOpenHelper = new ExternalDbOpenHelper(this, DB_NAME);
database = dbOpenHelper.openDataBase();
Cursor cursor = database.rawQuery("SELECT * FROM bywords", null);
ListView list4=(ListView)findViewById(R.id.list4) ;
String[] from = {WORDS_AR }; // _id mandatory
int[] to = new int[]{R.id.alquran_text};
CursorAdapter adapter = new SimpleCursorAdapter(
mContext,
R.layout.list_item,
cursor,
from,
to,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
list4.setAdapter(adapter);
And Then Second :
trans1 = new ArrayList<String>();
String[] allColumns2 = new String[] { FRIEND_ID,
BN_TRANS };
Cursor friendCursor2 = database.query(TABLE_NAME, allColumns2, null,
null, null, null, null);
if (friendCursor2 != null) {
friendCursor2.moveToFirst();
}
//return friendCursor;
if(!friendCursor2.isAfterLast()) {
do {
String name = friendCursor2.getString(1);
trans1.add(name);
} while (friendCursor2.moveToNext());
}
friendCursor2.close();
String [] bntrans1= new String[trans1.size()];
bntrans1 = trans1.toArray(bntrans1);
ListAdapter adapter = new ArrayAdapter<String>(
getApplicationContext(), R.layout.alert_row, seleted_route) {
ViewHolder holder;
Drawable icon;
class ViewHolder {
ImageView icon;
TextView title;
TextView title2;
}
public View getView(int position, View convertView,
ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) getApplicationContext()
.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(
R.layout.alert_row, null);
holder = new ViewHolder();
//holder.icon = (ImageView) convertView
// .findViewById(R.id.icon4);
holder.title = (TextView) convertView
.findViewById(R.id.title4);
holder.title2 = (TextView) convertView
.findViewById(R.id.title2);
convertView.setTag(holder);
} else {
// view already defined, retrieve view holder
holder = (ViewHolder) convertView.getTag();
}
Drawable drawable = getResources().getDrawable(R.drawable.iqra1); //this is an image from the drawables folder
holder.title.setText(seleted_route[position]);
holder.title2.setText(seleted_route[position]);
//holder.icon.setImageDrawable(drawable);
return convertView;
}
};
By the second way I can populate two column in two text view in one rows. But what I want that the corresponding rows with same id of rows must populate in one line of listview with verse I'd. The id's whataver are same in rows may be 5, 10 or twenty rows same id. Whenever the same Id They are one line, When the Id change the newline begins.

Splitting row into several nested objects

I've got the following query:
select id, name, email, supervisor, createdBy, createdAt, changedBy, changedAt from XXX
Now, I'd like to map each row into something that looks like this:
public class Organisation{
public int Id{get; set; }
public string Name{get; set; }
public ContactInfo Contact{get;set;}
public Action CreatedBy{get;set;}
public Action ChangedBy{get;set;}
}
public class ContactInfo{
public string Name{get;set;}
public string Email{get;set;}
}
public class Action{
public string Username{get;set;}
public DateTime At{get;set;}
}
I've seen some examples that show how to partition the info, but it seems like will not work in this example. Can it be done or should I map my query into a helper object which will then be mapped into these classes?
Thanks.
Luis
Dapper allows you to map a single row to multiple objects. This is a
key feature if you want to avoid extraneous querying and eager load
associations.
You can read about it more here
Below is an example:
[Test]
public void Test_Multi()
{
using (var conn = new SqlConnection(#"Data Source=.\sqlexpress;Integrated Security=true; Initial Catalog=foo"))
{
var result = conn.Query<Organisation, ContactInfo, Action, Action, Organisation>(
#"select Id = 100, Name = 'Foo',
Name = 'Contact Name', Email = 'contact#foo.com',
Username = 'The Creator', At = '12/25/2017',
Username = 'The Modifier', At = '12/26/2017' ",
(org, contact, cretedBy, changedBy) =>
{
org.Contact = contact;
org.CreatedBy = cretedBy;
org.ChangedBy = changedBy;
return org;
}, splitOn: "Id, Name, Username, Username").First();
Assert.That(result.Id, Is.EqualTo(100));
Assert.That(result.Contact.Email, Is.EqualTo("contact#foo.com"));
Assert.That(result.CreatedBy.Username, Is.EqualTo("The Creator"));
Assert.That(result.ChangedBy.Username, Is.EqualTo("The Modifier"));
}
}

Multi Mapping in Dapper. Receiving the error in SpiltOn

*Can You explain the Split on function in the multimap *
I am Trying to get the data from the Database using Dapper ORM. I have received the following error
System.ArgumentException : When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id
Parameter name: splitOn
public abstract class Domain
{
public Guid Id { get; set; }
}
public abstract class ItemBase : Domain
{
private IList<Image> images = new List<Image>();
public Guid? ParentId { get; set; }
public string Name { get; set; }
public IList<Image> Images { get { return images; } }
}
public class Meal : ItemBase
{
}
public class Item : ItemBase
{
private IList<Meal> meals = new List<Meal>();
public IList<Meal> Meals { get { return meals; } };
}
public class Image : Domain
{
public byte Img { get; set; }
public string Description { get; set; }
}
public class MealImageLink : Domain
{
public Guid ItemId { get; set; }
public Guid ImageId { get; set; }
}
/* search function to take dat from the table */
private List<Meal> SearchMeals(Guid id)
{
var query = #"SELECT meal.[Name],meal.[Description],meal.
[Price],mealImage.[Image] as Img
FROM [MealItems] as meal
LEFT JOIN [MealImageLink] mealImageLink
on meal.Id= mealImageLink.MealItemId
LEFT JOIN [Images] mealImage on
mealImageLink.ImageId=mealImage.Id
WHERE meal.[ParentId]=#Id";
List<Meal> meals = ( _connection.Query<Meal, MealImageLink, Image, Meal>
(query, (meal, mealLink, mealImage) =>
{
meal.Images.Add(mealImage);
return meal;
}, new { #Id = id })).ToList();
return meals;
}
The multi-map feature is really more intended for scenarios like:
select foo.Id, foo.whatever, ...,
bar.Id, bar.something, ...,
blap.Id, blap.yada, ...
from foo ...
inner join bar ...
left outer join blap ...
or the lazier but not uncommon:
select foo.*, bar.*, blap.*
from ...
inner join bar ...
left outer join blap ...
But in both of these cases, there is a clear and obvious way to split the horizontal range into partitions; basically, whenever you see a column called Id, it is the next block. The name Id is configurable for convenience, and can be a delimited list of columns for scenarios where each table has a different primary key name (so User might have UserId, etc).
Your scenario seems quite different to this. It looks like you're currently only selecting 4 columns with no particular way of splitting them apart. I would suggest that in this case, it is easier to populate your model via a different API - in particular, the dynamic API:
var meals = new List<Meal>();
foreach(var row in _connection.Query(sql, new { #Id = id }))
{
string name = row.Name, description = row.Description;
decimal price = row.Price;
// etc
Meal meal = // TODO: build a new Meal object from those pieces
meals.Add(meal);
}
The dynamic API is accessed simply by not specifying any <...>. With that done, columns are accessed by name, with their types implied by what they are being assigned to - hence things like:
decimal price = row.Price;
Note: if you want to consume the row data "inline", then just cast as soon as possible, i.e.
// bad: forces everything to use dynamic for too long
new Meal(row.Name, row.Description, row.Price);
// good: types are nailed down immediately
new Meal((string)row.Name, (string)row.Description, (decimal)row.Price);
Does that help?
Tl;dr: I just don't think multi-mapping is relevant to your query.
Edit: here's my best guess at what you intend to do - it simply isn't a good fit for multi-map:
var meals = new List<Meal>();
foreach (var row in _connection.Query(query, new { #Id = id })) {
meals.Add(new Meal {
Name = (string)row.Name,
Images = {
new Image {
Description = (string)row.Description,
Img = (byte)row.Img
}
}
});
}
return meals;

javaFX - tableview getting selected items

How can I get a selected item in a row from tableview in javaFX with Sqlite Database.
I am now using this to get the Index of the row:
(...)
#FXML
private ObservableList<UserData> data;
#FXML
private TableView table;
#FXML
private void pressTableAction() {
System.out.println("Selected index: " +table.getSelectionModel().getSelectedIndex());
}
(...)
public void initialize (URL url, ResourceBundle rb) {
try {
con = DataBaseConnection.getConnected();
stat = con.createStatement();
data = FXCollections.observableArrayList();
ResultSet rs = con.createStatement().executeQuery("SELECT * FROM person");
while (rs.next()) {
data.add(new UserData(rs.getInt("p_id"), rs.getString("Name")));
}
column1.setCellValueFactory(new PropertyValueFactory("p_id"));
column2.setCellValueFactory(new PropertyValueFactory("Name"));
table.setItems(null);
table.setItems(data);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error on Building Data");
}
Public static class UserData {
private IntegerProperty p_id;
private StringProperty Name;
private UserData(Integer p_id, String Name) {
this.p_id = new SimpleIntegerProperty (p_id);
this.Name = new SimpleStringProperty(Name);
}
public IntegerProperty p_idProperty() {
return p_id;
}
public StringProperty NameProperty() {
return Name;
}
}
My db looks like this:
CREATE TABLE person (p_id INTEGER PRIMARY KEY AUTOINCREMENT, Name VARCHAR(30) NOT NULL);
How can I get the p_id or the Name of the row I clicked?
#FXML
private void pressTableAction() {
System.out.println("Selected index: " + table.getSelectionModel().getSelectedIndex());
System.out.println("Selected p_id: " + ????)
}
First, do not use raw types for your table and table columns. Your IDE should be generating lots of warnings for this. So you should do
#FXML
TableView<UserData> table ;
instead of the declaration you have. Similarly the columns should be declared with the appropriate types.
If your model class UserData follows the standard JavaFX properties pattern, it will have a getP_id() method, and you can do
UserData selected = table.getSelectionModel().getSelectedItem();
System.out.println("Selected p_id: "+selected.getP_id());

Manually map column names with class properties

I am new to the Dapper micro ORM. So far I am able to use it for simple ORM related stuff but I am not able to map the database column names with the class properties.
For example, I have the following database table:
Table Name: Person
person_id int
first_name varchar(50)
last_name varchar(50)
and I have a class called Person:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Please note that my column names in the table are different from the property name of the class to which I am trying to map the data which I got from the query result.
var sql = #"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(sql).ToList();
return person;
}
The above code won't work as the column names don't match the object's (Person) properties. In this scenario, is there anything i can do in Dapper to manually map (e.g person_id => PersonId) the column names with object properties?
Dapper now supports custom column to property mappers. It does so through the ITypeMap interface. A CustomPropertyTypeMap class is provided by Dapper that can do most of this work. For example:
Dapper.SqlMapper.SetTypeMap(
typeof(TModel),
new CustomPropertyTypeMap(
typeof(TModel),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName))));
And the model:
public class TModel {
[Column(Name="my_property")]
public int MyProperty { get; set; }
}
It's important to note that the implementation of CustomPropertyTypeMap requires that the attribute exist and match one of the column names or the property won't be mapped. The DefaultTypeMap class provides the standard functionality and can be leveraged to change this behavior:
public class FallbackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetMember(columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException nix)
{
// the CustomPropertyTypeMap only supports a no-args
// constructor and throws a not implemented exception.
// to work around that, catch and ignore.
}
}
return null;
}
// implement other interface methods similarly
// required sometime after version 1.13 of dapper
public ConstructorInfo FindExplicitConstructor()
{
return _mappers
.Select(mapper => mapper.FindExplicitConstructor())
.FirstOrDefault(result => result != null);
}
}
And with that in place, it becomes easy to create a custom type mapper that will automatically use the attributes if they're present but will otherwise fall back to standard behavior:
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}
That means we can now easily support types that require map using attributes:
Dapper.SqlMapper.SetTypeMap(
typeof(MyModel),
new ColumnAttributeTypeMapper<MyModel>());
Here's a Gist to the full source code.
This works fine:
var sql = #"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(sql).ToList();
return person;
}
Dapper has no facility that allows you to specify a Column Attribute, I am not against adding support for it, providing we do not pull in the dependency.
For some time, the following should work:
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
I do the following using dynamic and LINQ:
var sql = #"select top 1 person_id, first_name, last_name from Person";
using (var conn = ConnectionFactory.GetConnection())
{
List<Person> person = conn.Query<dynamic>(sql)
.Select(item => new Person()
{
PersonId = item.person_id,
FirstName = item.first_name,
LastName = item.last_name
}
.ToList();
return person;
}
Here is a simple solution that doesn't require attributes allowing you to keep infrastructure code out of your POCOs.
This is a class to deal with the mappings. A dictionary would work if you mapped all the columns, but this class allows you to specify just the differences. In addition, it includes reverse maps so you can get the field from the column and the column from the field, which can be useful when doing things such as generating sql statements.
public class ColumnMap
{
private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();
public void Add(string t1, string t2)
{
forward.Add(t1, t2);
reverse.Add(t2, t1);
}
public string this[string index]
{
get
{
// Check for a custom column map.
if (forward.ContainsKey(index))
return forward[index];
if (reverse.ContainsKey(index))
return reverse[index];
// If no custom mapping exists, return the value passed in.
return index;
}
}
}
Setup the ColumnMap object and tell Dapper to use the mapping.
var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");
SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));
An easy way to achieve this is to just use aliases on the columns in your query.
If your database column is PERSON_ID and your object's property is ID, you can just do
select PERSON_ID as Id ...
in your query and Dapper will pick it up as expected.
Taken from the Dapper Tests which is currently on Dapper 1.42.
// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);
Helper class to get name off the Description attribute (I personally have used Column like #kalebs example)
static string GetDescriptionFromAttribute(MemberInfo member)
{
if (member == null) return null;
var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
return attrib == null ? null : attrib.Description;
}
Class
public class TypeWithMapping
{
[Description("B")]
public string A { get; set; }
[Description("A")]
public string B { get; set; }
}
Before you open the connection to your database, execute this piece of code for each of your poco classes:
// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));
Then add the data annotations to your poco classes like this:
public class Section
{
[Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
public int Id { get; set; }
[Column("db_column_name2")]
public string Title { get; set; }
}
After that, you are all set. Just make a query call, something like:
using (var sqlConnection = new SqlConnection("your_connection_string"))
{
var sqlStatement = "SELECT " +
"db_column_name1, " +
"db_column_name2 " +
"FROM your_table";
return sqlConnection.Query<Section>(sqlStatement).AsList();
}
Messing with mapping is borderline moving into real ORM land. Instead of fighting with it and keeping Dapper in its true simple (fast) form, just modify your SQL slightly like so:
var sql = #"select top 1 person_id as PersonId,FirstName,LastName from Person";
If you're using .NET 4.5.1 or higher checkout Dapper.FluentColumnMapping for mapping the LINQ style. It lets you fully separate the db mapping from your model (no need for annotations)
This is piggy backing off of other answers. It's just a thought I had for managing the query strings.
Person.cs
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public static string Select()
{
return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
}
}
API Method
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(Person.Select()).ToList();
return person;
}
The simple solution to the problem Kaleb is trying to solve is just to accept the property name if the column attribute doesn't exist:
Dapper.SqlMapper.SetTypeMap(
typeof(T),
new Dapper.CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName) || prop.Name == columnName)));
The easier way (same as #Matt M's answer but corrected and added fallback to default map)
// override TypeMapProvider to return custom map for every requested type
Dapper.SqlMapper.TypeMapProvider = type =>
{
// create fallback default type map
var fallback = new DefaultTypeMap(type);
return new CustomPropertyTypeMap(type, (t, column) =>
{
var property = t.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(typeof(ColumnAttribute))
.Cast<ColumnAttribute>()
.Any(attr => attr.Name == column));
// if no property matched - fall back to default type map
if (property == null)
{
property = fallback.GetMember(column)?.Property;
}
return property;
});
};
for all of you who use Dapper 1.12, Here's what you need to do to get this done:
Add a new column attribute class:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute(string name)
{
this.Name = name;
}
}
Search for this line:
map = new DefaultTypeMap(type);
and comment it out.
Write this instead:
map = new CustomPropertyTypeMap(type, (t, columnName) =>
{
PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName));
return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
});
I know this is a relatively old thread, but I thought I'd throw what I did out there.
I wanted attribute-mapping to work globally. Either you match the property name (aka default) or you match a column attribute on the class property. I also didn't want to have to set this up for every single class I was mapping to. As such, I created a DapperStart class that I invoke on app start:
public static class DapperStart
{
public static void Bootstrap()
{
Dapper.SqlMapper.TypeMapProvider = type =>
{
return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
(t, columnName) => t.GetProperties().FirstOrDefault(prop =>
{
return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName);
}
));
};
}
}
Pretty simple. Not sure what issues I'll run into yet as I just wrote this, but it works.
Kaleb Pederson's solution worked for me. I updated the ColumnAttributeTypeMapper to allow a custom attribute (had requirement for two different mappings on same domain object) and updated properties to allow private setters in cases where a field needed to be derived and the types differed.
public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
prop.GetCustomAttributes(true)
.OfType<A>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
//
}
}

Resources