Pass multiple Filters to Android Room Dao SQL Query from ViewModel with Repository - android-room

I am using the below to fetch Database rows to my Adapter, however I want to return rows from multi-filtered single query using either "LIKE" and/or "WHERE" and basically all sql query filter types, I can do one filter via MutableLiveData<String>();
end result would be like ...
#Query("SELECT * FROM mytable WHERE suburb LIKE '%' || :suburb || '%' postcode LIKE '%' || :postcode || '%' BETWEEN firstDate AND lastDate")
fun getFilteredRows(
suburb: String?,
postcode: String?,
firstDate: String?,
lastDate: String?): LiveData<List<MyTable>>
As per below, currently way only can pass one filter var.
ViewModel Class
class MyViewModel internal constructor(repository: MyRepository) : ViewModel()
//filter by suburb
var suburb = MutableLiveData<String>().apply {
//do I set as HashMap??
value = SUBURB
}
//LiveData Observer access
val filteredRows: LiveData<List<MyTable>> = suburb.switchMap {
//pass multiple filters to repository here
//but currently only can pass one string to repository
repository.getFilteredRows(it)
}
//MyViewModel function to set the suburb value
fun setSuburb(_suburb: String) {
suburb.value = _suburb
}
//companion object
companion object {
var SUBURB: String? = null
}
Repository Class
class Repository private constructor(private val dao: Dao)
//basic repo to dao communtication
fun getFilteredRows(suburb: String?) = dao.getFilteredRows(suburb)
Dao Interface
#Dao
interface Dao
//here I want to receive multiple Strings to do filtering within the query
#Query("SELECT * FROM mytable WHERE suburb LIKE '%' || :suburb || '%'")
fun getFilteredRows(suburb: String?): LiveData<List<MyTable>>
I have tried with passing basic var Strings with no luck, seems only MutableLiveData is the way to pass variable to the Dao via ViewModel & Repository

** See Edit Below **
Not ideal to say the least, actually would not recommend, however, current work around is to "loop" through multiple MutableLiveData variables
ViewModel Class
var suburb = MutableLiveData<String>().apply { value = SUBURB }
var postcode = MutableLiveData<String>().apply { value = POSTCODE }
var limit = MutableLiveData<Int>().apply { value = LIMIT }
val filteredRows: LiveData<List<MyTable>> =
suburb.switchMap {
//set suburb MutableLiveData
var suburb = it
postcode.switchMap {
//set postcode MutableLiveData
var postcode = it
}
limit.switchMap {
//set limit MutableLiveData
var limit = it
}
repository.getFilteredRows(suburb, postcode, limit)
}
/// EDIT ANSWER ///
Using HashMap to pass multiple filters (Strings) to Dao SQl Query.
Tested a returned what was expected, so confirming this works.
Foreseeable issue is when needing to pass Strings & Int etc, may have to refer back to passing as Strings only & then do parse.ToInt() etc on Int String Values
build HashMap in my Fragment to pass to MyViewModel
lateinit var myModel: LiveData<MyTable>
var filters = HashMap<String, String>()
filters.put("suburb", myModel.value!!.suburb)
filters.put("postcode", myModel.value!!.postcode)
with(viewModel) {
//pass my HashMap to my ViewModel for update/change/set on filters MutableLiveData HashMap variable
setFilters(filters)
}
MyViewModel Class
//initilise filters MutableLiveData HashMap variable
var filters = MutableLiveData<HashMap<String, String>>().apply { value = FILTERS }
//function to update/change/set filters MutableLiveData HashMap variable
//see setFilters(filters) used in above Fragment
fun setFilters(_filters: HashMap<String, String>) {
filters.value = _filters
}
//foreach on passed HashMap via switchMap{}
val filtered: LiveData<List<MyTable>> = filters.switchMap {
//initilise variables
var suburb = ""
var postcode = ""
//foreach loop on HashCookie :)
for (filter in it) {
if(filter.key.equals("suburb")){
suburb = filter.value
}else if(filter.key.equals("postcode")) {
postcode = filter.value
}
}
//pass strings to Dao
repository.getFiltered(suburb, postcode)
}
//companion object
companion object {
var FILTERS: HashMap<String, String>? = null
}
Repository Class
//send variables to the Dao Interface
fun getFiltered(suburb: String?, postcode: String?) = dao.getFiltered(suburb, postcode)
Dao Interface
#Query("SELECT * FROM mytable WHERE suburb LIKE '%' || :suburb || '%' AND postcode LIKE '%' || :postcode || '%' ")
fun getFiltered(suburb: String?, postcode: String?): LiveData<List<MyTable>>

Related

Custom Result handling by calling store procedure in Spring Data JPA

I have requirement to call store procedures which takes input parameters. This store procedure returns custom result set, that result set i need to read and process further before return to UI. How we can achieve this?
EG:
#Query("CALL SP_EMPLOYEE_REPORT(:year)",nativeQuery = true)
List<EmpolypeeCustomReportBean> getEmployeeReport(#param("year") Integer year);
Given the following stored procedure.
CREATE PROCEDURE NAME_OF_THE_PROCEDURE(IN param VARCHAR(255), OUT retval INT)
You can call it from interface query:
#Procedure(value = "NAME_OF_THE_PROCEDURE")
int getFromStoredProcedure(String param);
Also by #Query annotation:
#Query(value = "CALL NAME_OF_THE_PROCEDURE(:input_value);", nativeQuery = true)
Integer findSomeThing(#Param("input_value") Integer name);
Or you can use named stored procedure query too.
#Entity
#NamedStoredProcedureQuery(name = "MyObj.getSomethingFromProc",
procedureName = "NAME_OF_THE_PROCEDURE", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "param", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "retval", type = Integer.class)})
public class MyObj{
// class definition
}
Then call it.
#Procedure(name = "MyObj.getSomethingFromProc")
Integer getSomethingFromStoredProc(#Param("param") String model);
Also you can use resultClasses and resultSetMapping properties in #NamedStoredProcedureQuery for complex return types.
Complex example provided by Eclipselink:
#NamedStoredProcedureQuery(
name="ReadUsingMultipleResultSetMappings",
procedureName="Read_Multiple_Result_Sets",
resultSetMappings={"EmployeeResultSetMapping", "AddressResultSetMapping", "ProjectResultSetMapping", "EmployeeConstructorResultSetMapping"}
)
#SqlResultSetMappings({
#SqlResultSetMapping(
name = "EmployeeResultSetMapping",
entities = {
#EntityResult(entityClass = Employee.class)
}
),
#SqlResultSetMapping(
name = "EmployeeConstructorResultSetMapping",
classes = {
#ConstructorResult(
targetClass = EmployeeDetails.class,
columns = {
#ColumnResult(name="EMP_ID", type=Integer.class),
#ColumnResult(name="F_NAME", type=String.class),
#ColumnResult(name="L_NAME", type=String.class),
#ColumnResult(name="R_COUNT", type=Integer.class)
}
)
}
)
})
public Employee(){
....
}

Kotlin entered value not searching database

We have worked on this code to error trap a value entered in a Edit Text field
When the value is entered correctly we are informed that the entered value does not match
BUT if we select the value from a recycler view list and populate the Edit Text field with the value the search tells us we have a match
Here is the code for the search in the DBHelper
fun getOneName(id: Int): Contact? {
val db = this.writableDatabase
val selectQuery = "SELECT * FROM $TABLE_NAME WHERE $colId = ?"
db.rawQuery(selectQuery, arrayOf(id.toString())).use { // .use requires API 16
if (it.moveToFirst()) {
val result = Contact(id = 0,name ="")
result.id = it.getInt(it.getColumnIndex(colId))
result.name = it.getString(it.getColumnIndex(colName))
return result
}
}
return null
}
We used this for the Model Class our first time using data class as just plain class
data class Contact (
var id: Int,
var name: String
)
And here is the button click that manages the search
btnGetID.setOnClickListener {
if(etPerson.text.toString().trim().isNullOrEmpty()){
message("Enter Contact Name")
return#setOnClickListener
}
var numeric = true
var string = etPerson.text.toString().trim()
numeric = string.matches(".*\\d+.*".toRegex())
if(numeric){
message("No NUMBERS")
return#setOnClickListener
}
val dbManager = DBHelper(this)
var name = etPerson.text.toString()
//val contact = dbManager.getOneName(name)
val contact = dbManager.getOneName(id.toInt())
if(contact?.name.equals(name)){
println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! contact ID= "+contact)
etPerson.setText("The contact name is $name the ID is "+contact?.id.toString())
}else{
etPerson.setText("Name NOT = to $name and the ID is "+contact?.id.toString())
}
}
We know the name Sally is in the DB if we type Sally in the else statement shows Name NOT = bla
If we select Sally from the Recyclerview List the first statement shows The contact name bla bla
Kotlin 1.2.71 API 27
Our question is why is the hand typed name failing if it mataches?
HERE IS THE CORRECT CODE FOR THE DBHelper
fun getOneName(name: String): Contact? {
val db = this.writableDatabase
val selectQuery = "SELECT * FROM $TABLE_NAME WHERE $colName = ?"
db.rawQuery(selectQuery, arrayOf(name)).use { // .use requires API 16
if (it.moveToFirst()) {
val result = Contact(id = 0,name ="")
result.id = it.getInt(it.getColumnIndex(colId))
result.name = it.getString(it.getColumnIndex(colName))
return result
}
}
return null
}

How to call Expression Func with two input parameters

I have following Expression Func which is receiving two input paramters, first is Person Object, second is bool and returning another type of Object PersonProfile
private Exression<Func<Person, bool, PersonProfile>> PersonProfileProjection => (person, isValid) =>
new PersonProfile
{
FirstName = person.FirstName,
HasAddress = isValid ? person.Address1 : null
};
And I am trying to call this while fetching Person table from dbContext.
_dbContext.Persons.Select(PersonProfileProjection);
I am confused how to send boolean parameter inside PersonProfileProjection. It works when I only put one input and one output parameter like this. But I want extra boolean input as well.
Any help would be highly appreciated.
You can follow Microsoft documentation for this : Expression Class
One sample created for SQLite that show above function usage.
public void GetData()
{
var connection = new SQLiteConnection(#"Data Source=database.sqlite;Version=3;");
var context = new DataContext(connection);
connection.Open();
var createtableQuery = #"
drop table Company;
CREATE TABLE[Company]
(
[ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
[Seats] INTEGER NOT NULL
);
";
var command = new SQLiteCommand(createtableQuery, connection);
command.ExecuteNonQuery();
Company com = new Company()
{
Id = 6,
Seats = 7
};
context.GetTable<Company>().InsertOnSubmit(com);
context.SubmitChanges();
var companies = context.GetTable<Company>();
foreach (var company in companies)
{
Console.WriteLine("Company: {0} {1}",
company.Id, company.Seats);
}
//compile Expression using Compile method to invoke it as Delegate
Func<int,int, Company> PersonProfileProjectionComp = PersonProfileProjection.Compile();
var dd = companies.Select(p => PersonProfileProjectionComp(p.Id,p.Seats));
//// Below line inline use. Both works.
//var dd = companies.Select(p => PersonProfileProjection.Compile().Invoke(p.Id,p.Seats));
}
private System.Linq.Expressions.Expression<Func<int, int, Company>> PersonProfileProjection => (person, seats) =>
new Company
{
Id = person,
Seats = seats
};
or in one line use this :
PersonProfileProjection.Compile().Invoke(person, isValid)
You could declare it as a Func instead of an expression:
private Func<Person, bool, PersonProfile> PersonProfileProjection => (person, isValid) =>
new PersonProfile
{
FirstName = person.FirstName,
HasAddress = isValid // do what you meant to do
};
... and call it as:
_dbContext.Persons.Select(p => PersonProfileProjection(p, true));
You could as well write an ordinary method:
private PersonProfile PersonProfileProjection(Person person, bool isValid)
{
return new PersonProfile
{
FirstName = person.FirstName,
HasAddress = isValid // do what you want to do
};
}
...and call it the same way:
_dbContext.Persons.Select(p => PersonProfileProjection(p, true));

decimal? with Left outer join gets null reference in LINQ

I am trying to do left outer join in LINQ for two vars but on selecting required coloumns, I get Object reference not set to an instance of an object error where I want Nullable decimal.
var FLS = (from ee in SumTillFYEnd
join es in SumTillFYStart on ee.Account equals es.Account into temp
from t in temp.DefaultIfEmpty()
select new
{
Account = ee.Account, // As of here it works
BeginDr = (t.DrStartCF == 0) ? (decimal?) null : t.DrStartCF // Here I get error Object reference not set to an instance of an object.
});
Some times SumTillFYEnd and some times SumTillFYStart becomes null. I want to join should work with default values, in case any one or both is null.
The problem is attempting to cast null to decimal?. You cannot ever directly cast null to another type, nullable or not. That will always cause a NullReferenceException. What you want instead is default. In other words, replace:
(decimal?)null
With
default(decimal?)
I solved this using a default class.
The reason I am seeing is that decimal can not be null so it either needs to set for a default value either 0 or decimal.MinValue
So, you require to have default class for SumTillFYStart like
var defaultSumTillFYStart = new SumTillFYStart { Account = string.Empty, DrStartCF =0};
With above in context, then in your piece of code replace
from t in temp.DefaultIfEmpty()
with this
from t in temp.DefaultIfEmpty(defaultSumTillFYStart)
I have a linqPad working written below but for different subset; I think it will help somebody:
void Main()
{
List<Debtor> debtors = new List<Debtor>();
List<SecurityHolding> holdings = new List<SecurityHolding>();
//Initialize Debtor
debtors.Add(new Debtor(){
AccountId = "J1",
OutstandingValue = 501.95M
});
debtors.Add(new Debtor(){
AccountId = "J2",
OutstandingValue = 75.68M
});
debtors.Add(new Debtor(){
AccountId = "J3",
OutstandingValue = 100.01M
});
//Initialize Security Holding
holdings.Add(new SecurityHolding(){
AccountId = "J2",
SecurityHoldingValue = 100M
});
holdings.Add(new SecurityHolding(){
AccountId = "J3",
SecurityHoldingValue = 200M
});
var defaultHolding = new SecurityHolding { AccountId= string.Empty, SecurityHoldingValue = 0};
var result = (from d in debtors
join p in holdings
on d.AccountId equals p.AccountId into temp
from t in temp.DefaultIfEmpty(defaultHolding)
select new
{
AccountId = d.AccountId,
OutstandingValue = d.OutstandingValue,
HoldingValue = (decimal?)t.SecurityHoldingValue
});
result.Dump();
}
// Define other methods and classes here
public class Debtor
{
public string AccountId {get;set;}
public decimal OutstandingValue {get;set;}
}
public class SecurityHolding
{
public string AccountId {get;set;}
public decimal SecurityHoldingValue {get;set;}
}
Here the output:

Need Help Translating SQL Server UNION Syntax to LINQ

I have the below SQL which works just fine:
SELECT Message, CreateDate, AccountId, AlertTypeId
FROM dbo.Alerts
UNION
SELECT TOP (100) PERCENT Status, CreateDate, AccountId,
(SELECT 10 AS Expr1) AS AlertTypeId
FROM dbo.StatusUpdates
WHERE AccountId = PassedInParameter
ORDER BY CreateDate DESC
I am trying to convert it to LINQ, which doesn't work just fine :) Obviously, there is a lot wrong here - it is just a rough start. It does not account for the above temp column or the order by condition and the generics / return type ambiguity is my attempt to make sense of the two different return types:
public List<T> GetSomething<T>(Int32 accountId)
{
List<T> result;
using (DataContext dc = _conn.GetContext())
{
IEnumerable<Alert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IEnumerable<StatusUpdate> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s);
IEnumerable<T> obj = alerts.Union(updates);
result = obj.ToList();
}
return result;
}
The problems I am having are:
1) I am dealing with two different types (Alerts and StatusUpdate) in my selects and
I am not sure how to combine them (or what type to return). I am guessing this might
be solved with generics?
2) In my SQL, I have this code: (SELECT 10 AS Expr1) AS AlertTypeId which adds the value ten to the temp column AlertTypeId (allowing the union to match it to Alert's real column AlertTypeId). How are temp columns such as this accomplished in LINQ / how do I do this?
Thanks for your help.
EDIT---------------------------------EDIT------------------------------------------EDIT
OK, I am a little further along. Below is what I have currently. You will notice I added some logic to return the updates for friend relations. I also made this a generic method of type IList given that alerts and updates have to be generic to agree. I pass in StatusUpdate in the calling method (further down below).
public IList GetUpdatesByAccountId<T>(Int32 accountId)
{
List<Friend> friends = _friendRepository.GetFriendsByAccountId(accountId);
using (DataContext dc = _conn.GetContext())
{
// Get all the account ids related to this user
var friendAccountIds =
friends.Select(friend => friend.MyFriendsAccountId).Distinct();
friendAccountIds = friendAccountIds.Concat(new[] { accountId });
var updates =
dc.StatusUpdates.Where(s => s.AccountId.HasValue && friendAccountIds.Contains(s.AccountId.Value)).Select(
s => new { Alert = (Alert)null, StatusUpdate = s});
var alerts =
dc.Alerts.Where(a => a.AccountId == accountId).Select(
a => new {Alert = a, StatusUpdate = (StatusUpdate) null});
var obj = updates.Union(alerts).Take(100);
return obj.OrderByDescending(su => su.StatusUpdate.CreateDate).ToList();
}
}
And, the calling method:
protected void LoadStatus()
{
repStatusUpdates.DataSource = _statusRepository
.GetUpdatesByAccountId<StatusUpdate>(_userSession.CurrentUser.AccountId);
repStatusUpdates.DataBind();
}
AND here are the interfaces to the repositories I am using to access my Alert and StatusUpdate tables via LINQ:
public interface IAlertRepository
{
List<Alert> GetAlertsByAccountId(Int32 accountId);
void SaveAlert(Alert alert);
void DeleteAlert(Alert alert);
}
public interface IStatusUpdateRepository
{
StatusUpdate GetStatusUpdateById(Int32 statusUpdateId);
List<StatusUpdate> GetStatusUpdatesByAccountId(Int32 accountId);
List<StatusUpdate> GetFriendStatusUpdatesByAccountId(Int32 accountId, Boolean addPassedInAccount);
void SaveStatusUpdate(StatusUpdate statusUpdate);
List<StatusUpdate> GetTopNStatusUpdatesByAccountId(Int32 accountId, Int32 number);
List<StatusUpdate> GetTopNFriendStatusUpdatesByAccountId(Int32 accountId, Int32 number, Boolean addPassedInAccount);
}
Current Problems:
1) When I compile this code, I get this strange error:
Unable to cast object of type
'System.Data.Linq.SqlClient.SqlNew' to
type
'System.Data.Linq.SqlClient.SqlValue'.
The only reading I can find on it is this link although there isn't a clear solution there (at least that I can tell). However, if the above LINQ code does not look good to you, maybe whatever you suggest will cause this error to disappear.
2) The above code is still not accounting for this line from the original SQL:
(SELECT 10 AS Expr1) AS AlertTypeId
but this is minor.
Thanks again for the help.
Try this (i converted the StatusUpdate to an alert, if this isn't acceptable, you're going to have to either convert the Alert to a StatusUpdate, or create a new class):
var alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
var updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100)
.Select( x => new Alert
{
Message = x.Percent.ToString(),
CreateDate = x.CreateDate,
AccountId = x.AccountId,
AlertTypeId = 10 // Is this right?
}
);
var obj = alerts.Union(updates);
result = obj.ToList();
The reason I do the Select last is so that you don't have to construct a new alert for all the results your are not using.
This will give you a list of Alerts.
Using a generic in this situation is sort of hard to pull off. For instance, you can't do this:
IQueryable alerts = (from a in _alerts
where a.AccountId == accountId
select a);
Because that implicitly converts a to type T. Even if you try to limit what T implements or inherits from:
public List<T> GetSomething<T>(Int32 accountId) where T : IAlert// Interface that both StatusUpdates and IAlert implement
public List<T> GetSomething<T>(Int32 accountId) where T : Alert
public List<T> GetSomething<T>(Int32 accountId) where T : AlertBase // Base class for both Status and Alert
You'll still run into problems because there is no way to statically know exactly what type T is, so you cannot know if it can be converted from Alert and StatusUpdate.
An alternative is to explicitly use IAlert as your return type:
public List<IAlert> GetSomething(Int32 accountId)
With IAlert:
public interface IAlert
{
int AccountId { get; set; }
int AlertTypeId { get; set; }
DateTime CreateDate { get; set; }
string Message { get; set; }
}
If you have have both Alert and StatusUpdate implement IAlert, you could rewrite it as so:
IQueryable<IAlert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IQueryable<IAlert> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100);
var obj = alerts.Union(updates);
result = obj.ToList();
This is the route I would take instead of passing in some unknown type and trying to limit what it implements or inherits, because casting to that type might still be invalid.
You can only take unions of sequences of equal types. You need to convert alerts and updates to sequences of a common type, then take the union. You can do so using anonymous types. Especially useful if the types don't have anything in common.
//this is a hack and probably not what you would want to use.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new { Alert = a, StatusUpdate = (StatusUpdate)null };
var updates =
from s in dc.StatusUpdates
where s.AccountId == accountId
select new { Alert = (Alert)null, StatusUpdate = s };
//both are sequences of anonymous type with properties:
// Alert (of type Alert)
// StatusUpdate (of type StatusUpdate)
var obj = alerts.Union(updates);
If you have fields in common, you'd still use anonymous types except you'd include the known fields.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new
{
a.Message, //assuming is a string
Status = (string)null,
a.CreateDate,
a.AccountId,
a.AlertTypeId //assuming is an int
};
var updates =
(from s in dc.StatusUpdates
where s.AccountId == accountId
select new
{
Message = (string)null,
s.Status, //assuming is a string
s.CreateDate,
s.AccountId,
AlertTypeId = 10 //this should handle the "10 AS AlertTypeId" part
}).OrderByDescending(s => s.CreateDate);
var obj = alerts.Union(updates);
The key is that both anonymous types has the same exact properties of the same exact types. Then you can take the union between them both.

Resources