Cache evict on every DML operation - spring

I have couple of findBy... methods in my spring application for instance
findById(int id)
findByName(String name)
findByCity(String city)
etc...
For every method I have a cache key like
1. person.id.<id_val> e.g person.id.1, person.id.2 ....
2. person.name.<name_val> e.g person.name.foo, person.name.bar ....
2. person.city.<city_val> e.g person.city.abc, person.city.xyz ....
so, e.g:
Person p = {id = 1, name = "foo", city = "xyz"}
when invoking above methods
1. findById(int id) id = 1 // return p {id = 1, name = "foo", city = "xyz"}
2. findByName(String name) name = foo // return p {id = 1, name = "foo", city = "xyz"}
3. findByCity(String city) city = xyz // return p {id = 1, name = "foo", city = "xyz"}
It is a Read Through cache i.e when data will be read from database I am populating data in cache.
Requirement: When any DML operation (Insert, Update, Delete) operation is to be performed on Person object I need to evict the cache, not doing so will keep stale data in cache till it is auto flushed.if an update happens on city for user 1 and the new value is city = "pqr" then I need to evict data for all the keys else user will get stale data
Problem: As mentioned above I have couple of different cache keys so,
how should I find all the keys which are storing the data of person p{id = 1, name = "foo", city = "xyz"} and evict them?
Any help will be appreciated.
I have thought of reverse mapping of keys to maintain all the keys for particular Id like
person.id.1 = {person.name.foo, person.city.xyz}
and when on any DML operation on particular object use the id and find all the keys from cache and evict those.
It seems to be space and time intensive operation, so any better approach to do it?

Related

How to update room database and How to get the insert status is working or completed?

The first:
I've got dataList from retrofit And insert Room Database.
I want to change dataList(Like insert a element). My Room Database can work because I used OnConflictStrategy.REPLACE. but when I delete dataList some elements, My Room Database can not delete elements.
Dao:
#Insert (onConflict = OnConflictStrategy.REPLACE)
suspend fun insertData(dataList : List<Data>)
Entity:
#Entity
data class Data(
#PrimaryKey val Id : Long,
val Fl : String,
val FlMc : String,
val Dm : String,
val Mc : String,)
ViewModel:
fun insertData(dataList: List<Data>) = viewModelScope.launch {
dataRepository.insertData(dataList)
}
//get data from server
fun getData():LiveData<List<Data>>
Activity:
dataViewModel.getData().observer(this){
dataViewModel.insertData(it)
}
How to resolve this situation except DELETE ALL THEN INSERT
The second:
I want to use a progressbar to indicate that I am inserting dataList
How to get the insert status is working or completed
If I understand correctly, you issue is that you cannot delete because you are building a DataList item but don't know the primary key value as it's generated.
As you haven't shown the DataList entity then assuming it is like:-
#Entity
data class DataList(
#PrimaryKey(autogenerate = true)
val id: Long,
val othercolumns: String
....
)
and if you change from suspend fun insertData(dataList : List<Data>) to suspend fun insertData(dataList : List<Data>): List<Long> (i.e. added the List as the result)
Then you have the values of the id column in the result. In the case above the value is the value of the id column.
If the #PrimaryKey is not an integer type e.g. a String then the long returned WILL NOT be the value of the primary key. It will be a special value known as the rowid.
In short using an integer with primary key makes the column an alias of the rowid. if not an integer primary key then it is not an alias BUT the rowid still exists.
You can still use the rowid to access a specific row as the rowid MUST be a unique value. e.g. (again assuming the above) you could have an #Query such as
#Query("SELECT * FROM the_datalist_table WHERE rowid=:rowid")
suspend fun getDataListById(rowid: Long)
Only of use if you know the rowid though.
You could get rowid's say by using
#Query("SELECT rowid FROM the_datalist_table WHERE othercolumns LIKE :something")
suspend fun getRowidOfSomeDataLists(something: String): List<Long>
still not of great use as the selection criteria would also be able to provide a list of Datalists.
Additional re the comment:-
How to use in viewModel or Activity?
As an example you could do something like :-
fun insertData(dataList: List<Data>) = viewModelScope.launch {
val insertedDataList: ArrayList<Data> = ArrayList()
val insertedIdList = dataRepository.insertData(dataList)
val notInsertedDataList: ArrayList<Data> = ArrayList()
for(i in 0..insertedIdList.size) {
if (insertedIdList[i] > 0) {
insertedDataList.add(
Data(
insertedIdList[i], //<<<<< sets the id as per the returned list of id's
dataList[i].Fl,
dataList[i].FlMc,
dataList[i].Dm,
dataList[i].Mc)
)
} else {
notInsertedDataList.add(
Data(
insertedIdList[i], //<<<<< sets the id as per the returned list of id's WILL BE -1 as not inserted
dataList[i].Fl,
dataList[i].FlMc,
dataList[i].Dm,
dataList[i].Mc
)
)
}
}
val notInsertedCount = notInsertedDataList.size
val insertedCount = insertedDataList.size
}
So you have :-
insertedDataList an ArrayList of the successfully inserted Data's (id was not -1) with the id set accordingly.
notInsertedDataList an ArrayList of the Data's that were not inserted (id was -1) id will be set to -1.
insertedCount an Int with the number inserted successfully.
notInsertedCount and Int with the number not inserted correctly.
DELETE ALL
To delete all rows, unless you extract all rows you can't use the convenience #Delete, as this works on being provided the Object (Data) and selecting the row to delete according to the primary key (id column).
The convenience methods #Delete, #Update, #Insert are written to generate the underlying SQL statement(s) bases upon the object (Entity) passed.
e.g. #Delete(data: Data) would generate the SQL DELETE FROM data WHERE id=?, where ? would be the value of the id field when actually run.
The simpler way to delete all columns is to use the #Query annotation (which handles SQL statements other than SELECT statements). So you could have.
#Query("DELETE FROM data")
fun deleteAllData()
note that this does not the return the number of rows that have been deleted.

Having trouble grouping columns in Linq query with multiple joins

I have an MVC ViewModel that I'd like to pass through to a Razor view. In the controller, I've created a database context and joined tables together using Linq. Once summed and grouped, I'm getting an error:
Error CS1061 'decimal' does not contain a definition for 'GroupBy' and no accessible extension method 'GroupBy' accepting a first argument of type 'decimal' could be found (are you missing a using directive or an assembly reference?
I've gone through almost every example on stack overflow and google and couldn't find an example that matched the structure of my query. Also, the MS examples are very trivial and are not of much use.
Here is the action in the controller:
public IHttpActionResult GetEmployeeReleasedAllocatedBonus(int eid)
{
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new EmployeeAllocatedReleasedBonusViewModel()
{
AllocatedToEmployee = br.Amount, AllocatedPercentage = bonus.Amount * 100
,
JobNumber = job.JobNumber, JobDescription = job.JobDescription
})
.ToList()
.Sum(s => s.AllocatedToEmployee)
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage});
return Ok(employeeReleasedAllocatedBonus);
}
It's worth mentioning that the AllocatedPercentage datatype is a decimal. However, I've tried changing it to string but the error message stays.
Also tried using the group functionality right before .ToList() but that didn't work either.
After ToList() you have a List<EmployeeAllocatedReleasedBonusViewModel>.
In Sum(s => s.AllocatedToEmployee), every s is one EmployeeAllocatedReleasedBonusViewModel. Apparently a EmployeeAllocatedReleasedBonusViewModel has a property AllocatedToEmployee which is probably of type decimal. This can be summed into one decimal.
The result of the Sum (a decimal) is the input of your GroupBy. Does type decimal have a method GroupBy? Of course it doesn't!
Alas you forgot to tell us your requirements. It is difficult to extract them from code that doesn't do what you want.
It seems to me that you have two one-to-many relations:
Employees have zero or more BonusReleases. Every BonusRelease belongs to exactly one Employee using foreign key
Jobs have zero or more BonusReleases. Every BonusRelease belongs to exactly one Job.
Now what do you want: do you want all JobNumbers and JobDescriptions of all Jobs with the total of their AllocatedPercentage? I'm not sure what the Employees do within this query.
Whenever you want items with their sub-items, like Schools with their Students, Customers with their Orders, Orders with their OrderLines, use GroupJoin. If you want it the other way round: Student with the School that he attends, Order with the Customer who placed the Order, use Join.
var result = dbContext.Jobs.GroupJoin(dbContext.BonusReleases,
job => job.Id, // from every Job take the primary key
bonusRelease => bonusReleas.JobId, // from every BonusRelease take the foreign key
// parameter ResultSelector: take every Job with all its BonusReleases to make a new:
(job, bonusReleasesOfThisJob) => new
{
JobNumber = job.JobNumber,
JobDescription = job.JobDescription
// do you want the total of all allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.Sum(),
// do something to make it a percentage
// or do you want a sequence of allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.ToList(),
});
Or do you want the JobNumber / JobDescription / Total allocated bonus per Employee?
var result = dbContext.Employees.GroupJoin(dbContext.BonusReleases,
employee => employee.Id, // from every Employee take the primary key
bonus => bonus.EmployeeId, // from every BonusRelease take the foreign key
(employee, bonusesOfThisEmployee) => new
{
// Employee properties:
EmployeeId = employee.Id,
EmpoyeeName = employee.Name,
// for the jobs: Join the bonusesOfThisEmployee with the Jobs:
Jobs = dbContext.Jobs.GroupJoin(bonusOfThisEmployee,
job => job.Id,
bonusOfThisEmployee => bonusOfThisEmployee.JobId,
(job, bonusesOfThisJob) => new
{
Number = job.Id,
Description = job.Description,
TotalBonus = bonusOfThisJob.Select(bonus => bonus.Amount).Sum(),
}),
});
Harald's comment was key - after ToList(), I had a list of . Therefore I took a step back and said what if I put the results into an anonymous object first. Then do the group by and then the sum, putting the final result into the view model. It worked. Here is the answer.
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new
{
AllocatedToEmployee = br.Amount
,AllocatedPercentage = bonus.Amount * 100
,JobNumber = job.JobNumber
,JobDescription = job.JobDescription
})
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage})
.Select(t => new EmployeeAllocatedReleasedBonusViewModel
{
JobNumber = t.Key.JobNumber,
JobDescription = t.Key.JobDescription,
AllocatedPercentage = t.Key.AllocatedPercentage,
AllocatedToEmployee = t.Sum(ae => ae.AllocatedToEmployee)
});

Need LINQ to EF Query for my given situation

I have to store profiles of farmers. For this I have MST_FarmerProfile as main table which contains personDetails_Id,bankDetails_Id,contactDetails_Id as foreign keys for storing personal details, bank details and contact Details in different tables.
Now the main thing is whenever i update a record, modified details are saved in same table while previous records are saved in personal details history, bank details history and contact Details history tables which again contains personDetails_Id,bankDetails_Id,contactDetails_Id respectively as foreign keys. Basically we need to keep trail of updated records. Following is my database schema:
I have tried using joins but the problem is suppose i update same record two times, each history table will contain 2 rows with same foreign key thus my join gives 2X2X2=8 records, whereas i need only 2.
I did this and it worked:
List<FarmerDetailsReport> fdr =
(from fp in RptdataAccess.MST_FarmerProfile
join pdh in RptdataAccess.PersonalDetailsHistory on fp.personDetails.Id equals pdh.PersonalDetails.Id
join cdh in RptdataAccess.ContactDetailsHistory on fp.contactDetails.Id equals cdh.ContactDetails.Id
join bdh in RptdataAccess.BankDetailsHistory on fp.bankDetails.Id equals bdh.BankDetails.Id
select new FarmerDetailsReport
{
FarmerURNNo = fp.URN_No,
CreatedBy = fp.UserId,
CreatedOn = fp.CreatedOn,
FarmerName = pdh.FarmersName,
FatherOrHusbandName = pdh.FatherHusbandName,
DateofBirth = pdh.DateOfBirth,
Gender = pdh.IsMale.ToLower() == "yes" ? "Male" : "Female",
MaritalStatus = pdh.MaritalStatus,
Religion = pdh.Religion.ToLower() == "other" ? pdh.otherReligion : pdh.Religion,
TypeofBank = bdh.bankType,
BankName = bdh.bankName,
BranchName = bdh.branchName,
BankAccountNo = bdh.bankAccountNo,
Address1 = cdh.Address1,
State = cdh.State,
District = cdh.District,
UpdatedOn = pdh.ChangedOn,
UpdatedBy = pdh.ChangedBy
} ).GroupBy(p => new { p.UpdatedOn }).Select(g => g.FirstOrDefault()).ToList();

Sorting a Wicket DataTable by a PropertyColumn that contains some null values

Here is some psuedo code for my table:
List<IColumn<Foo>> columns = new ArrayList<IColumn<Foo>>();
columns.add(new PropertyColumn<Foo>(Model.of("Name"), "name", "name"));
columns.add(new PropertyColumn<Foo>(Model.of("Leader"), "leader.lastName",
leader.fullName"));
fooTable = new AjaxFallbackDefaultDataTable<Foo>("fooTable", columns,
provider, 10);
The class Foo contains among other fields:
private String name;
private User leader;
And class User contains Strings userName, password, firstName, lastName, email, along with:
public String getFullName() {
// Use blank strings in place of "null" literals
String f = firstName != null ? firstName: "";
String l = lastName != null ? lastName: "";
return (l + ", " + f);
}
Every Foo has a name, and every User has has a first and last name. However, not every Foo has a leader, so in the table above, there are several null/ blank values for leader. When the table is sorted by leader by clicking the column header, all Foo's without a leader are missing. When sorted again by name, they return, with blanks for leader.
Is there anyway to fix this? A preference where nulls always sort last would be best, but I'll take anything where they don't just disappear.
The correct sorting is a responsibility of the SortableDataProvider. If it is making items disappear, you should revise it's logic.

Best Practice Checking for duplicate rows before inserting list of items

I have a an array of objects that I want to enter into the database.
My method call looks like this.
public void Add(CardElement[] cardElements){
foreach (var cardElement in cardElements)
{
Data.Entry(cardElement).State = System.Data.EntityState.Added;
}
Data.SaveChanges();
}
The database table resembles this
MS SQL = Table mytable Columns a,b,c,d,e,f
Unique Constraint a,b,c
The data I want to insert resembles this.
var obj [] = new [] {
new MyObject () { a = 1, b =1, c = 1 },
new MyObject () { a = 1, b =1, c = 2 }
new MyObject () { a = 1, b =1, c = 3 }
};
So, I want to check the database for these three rows before I add them to the database.
I could do something like but I assume this should cause some extra trips to the database.
private bool checkExists()...
foreach (var cardElement in cardElements)
{
var exists = (from ce in Data.CardElements
where ce.CardId == cardElement.CardId
where ce.Area == cardElement.Area
where ce.ElementName == cardElement.ElementName
select ce).Any();
if(exists return true)
}
return false
So, how could I handle this more gracefully?
Is it even worth trying to accomplish this using linq?
Should I write some stored procedures for performance?
I agree that you should let the db make the decision.
Please have a look at using UPSERT as stated in this post
Why not just attempt the insert and let the database tell you if any unique constraint violations have occurred (using try/catch)?
The problem is that even if you query data somebody else can insert the record between your query and saving changes. You will still have to handle exception for violating unique constraint despite your additional queries - and yes, every check will do additional trip to database.
If your main concern is performance use stored procedure where you can additionally use table hint to lock table for inserts during initial check for existence.

Resources