Context
I have problems using the method realm.add(object, update: true) successively.
I'm making WEB requests to my API, and I'm testing the case where internet connection is disabled. When a request failed and I get the response, I add an UnsynchronizedRequest realm object to Realm.
Problem
I have to test the case where I have to make multiples call to my API, so I will add multiple UnsynchronizedRequest objects to Realm.
I only start the next web request when I received the previous request response, so the requests are well chaining, there is no concurrent requests.
When I'm making only one request and that it failed, the object is well added to Realm.
But when I'm making more than one request, only the first is added to the Realm, and the others are not added.
What is strange is that when I'm using breakpoints, all objects are well added to Realm. But when I disable breakpoints, only the first UnsynchronizedRequest object is added to the Realm, the other are ignored.
I think it is a problem with the write thread, the doc says that it blocks other thread if multiple writes are concurrent, but I don't know how to solve it.
What I execute when a web request failed :
class func createUnsynchronizedRequest(withParameters parameters : Parameters, andRoute route : String){
print("Adding unsynchronized request to local...")
let failedRequest = UnsynchronizedRequest()
failedRequest.parameters = parameters
failedRequest.route = route
failedRequest.date = Date()
RealmManager.sharedInstance.addOrUpdate(object: failedRequest)
}
RealmManager.swift
func addOrUpdate(object : Object){
try! realm.write {
realm.add(object, update: true)
}
}
UnsynchronizedRequest.swift
#objcMembers class UnsynchronizedRequest : Object {
// MARK: - Realm Properties
override static func primaryKey() -> String? {
return "id"
}
dynamic var id = ""
dynamic var route = ""
dynamic var date : Date! {
didSet {
self.id = "\(self.date)"
}
}
}
I already tried to check if realm was in transaction in the addOrUpdate method before starting realm.write, this not solve the problem.
I also tried to catch the error with realm.write, but no error is thrown.
Furthermore, if I execute for example 3 time a web request et that is failed, I'm sure that my code is executed because the print in createUnsynchronizedRequest is working.
I really think it is a problem with write threads because when I use breakpoint to slow the code execution, everything works well.
Example with 3 failing web requests and USING BREAKPOINTS :
As you can see, using breakpoints, the 3 objects are well added to realm :
Example with the same 3 failing web requests and WITHOUT BREAKPOINTS :
But now without breakpoints, only the first object is added to Realm :
Any idea ?
As I created my UnsynchronizedRequest objects with the Date() method, the created objects had the same primary keys.
The space time between object creation is not sufficient to make a Date() object different than the previous created.
Related
In my app I am using a NSPersistantContainer with two NSManagedObjectContexts (viewContext & background context). All my read operations are performed on view context, while all my write operations are performed on the background context (shown below).
Creating a new Animal class
class func new(_ eid: String) {
//Create Animal Entity
let container = CoreDataService.persistentContainer
container.performBackgroundTask { (context) in
let mo = Animal(context: context)
mo.eid = eid
mo.lastModified = Date()
mo.purchaseDate = Date()
mo.uuid = UUID()
do {
try context.save()
}
catch let error {
print(error)
}
}
}
The problem I am having is I need to return the newly created NSManagedObject (Animal) back to the manager class, where the Animal.new(eid) was called, to be used to show the object properties.
I have experimented with using a completion handler, but had issues returning the value, as was using a background NSManagedObject in the main thread.
Using possible new function
Animal.new(eid) { (animal) in
if let animal = animal {
}
What is the best approach for returning a newly created NSManagedObject, created in a background context?
What I do is to keep my CoreData managed objects within my CoreDataManager class, not exposing them out to the rest of my framework. So methods that are to create one or more managed objects would accept the data as an unmanaged object, and then create the managed object but not return it (as callers already have the unmanaged object). When fetching from the CoreDataManager, I'd create and populate a unmanaged object(s) and return that.
Now part of why I do this is because I'm using CoreData in a framework, such that I'd never want to hand a managed object to a client app of my framework. But it also solves other problems like the one you described.
(When I write apps, I use Realm, and those objects can go straight to app's UI classes because Realm is so much easier to, erm, manage. :)
When we pass our DbContext an object whose values have not changed, and try to perform an Update we get a 500 internal server error.
A user may open a dialog box to edit a record, change a value, change it back and then send the record to the database. Also we provide a Backup and Restore function and when the records are restored, some of them will not have changed since the backup was performed.
I was under the impression that a PUT would delete and re-create the record so I didn't feel there would be a problem.
For example, having checked that the Activity exists my ActivityController is as follows:
var activityEntityFromRepo = _activityRepository.GetActivity(id);
// Map(source object (Dto), destination object (Entity))
_mapper.Map(activityForUpdateDto, activityEntityFromRepo);
_activityRepository.UpdateActivity(activityEntityFromRepo);
// Save the updated Activity entity, added to the DbContext, to the SQL database.
if (await _activityRepository.SaveChangesAsync())
{
var activityFromRepo = _activityRepository.GetActivity(id);
if (activityFromRepo == null)
{
return NotFound("Updated Activity could not be found");
}
var activity = _mapper.Map<ActivityDto>(activityFromRepo);
return Ok(activity);
}
else
{
// The save failed.
var message = $"Could not update Activity {id} in the database.";
_logger.LogWarning(message);
throw new Exception(message);
};
My ActivityRepository is as follows:
public void UpdateActivity(Activity activity)
{
_context.Activities.Update(activity);
}
If any of the fields have changed then we don't get the error. Do I have to check every record for equality before the PUT? It seems unnecessary.
Perhaps I have missed something obvious. Any suggestions very welcome.
There is a lot of code missing here.
In your code you call your SaveChangesAsync (not the EF SaveChangesAsync).
Probably (but there is not the code to be sure) your SaveChangesAsync is something that returns false if there is an exception (and is not a good pattern because you "loose" the exception info) or if DbSet.SaveChangesAsync returns 0.
I think (but there is a lot of missing code) that this is your case. If you don't make any changes, SaveChangesAsync returns 0.
EDIT
The System.Exception is raised by your code (last line). EF never throws System.Exception.
I have a class for products (MyProduct) and then another class (ProductAnalyzer) for performing various options on a collection of products.
I want to do unit testing on these classes. Specifically, testing if product is X days old or older.
There is already a function called ProductAnalyzer.tenDaysOld() that will do this. As long as instances of MyProduct are in the ProductAnalyzer collection, it will do the calculation.
In order to run tests, I need a way to somehow modify timestamps on MyProduct instances so they are older than 10 days. Or perhaps modify tenDaysOld(). Either way, I'm guessing they'll need to be mocked.
In MyProduct, productCreated is timestamped and private. I'd like to keep it that way but that means it can't be tested. If I modify it to public, I can test it but then I'm changing the code base just for testing purposes, which has nothing to do with actual functionality in production (isn't that bad practice?).
Any suggestions how I can approach this?
Below are simplified versions of the classes with relevant code shown.
class MyProduct: NSObject {
private var productCreated:Date
init(){
productCreated = Date()
}
}
class ProductAnalyzer {
static var products:[InventoryItem] = []
static func tenDaysOld() {
let tenDays = NSCalendar.current.date(byAdding: Calendar.Component.day, value: 10, to: Date())
let oldProducts = ProductAnalyzer.products.filter({ $0.dateAdded.compare(tenDays!) == .orderedDescending })
}
}
After struggling to create Realm object in App Group, I seem to have hit the next issue.
Objects that save into Realm with no issues when in Documents directory does not seem to save when in App Groups.
realm.transactionWithBlock { () -> Void in
let testModel = TestModel(test: "xyz")
self.realm.addObject(testModel)
}
Is this a known issue?
All mutations including adds must happen in a write transaction. Like this:
self.realm.transactionWithBlock() {
self.realm.addObject(testModel)
}
I'm attempting to query Solr from Angular and routing the request through a Play Controller for security and using Play redirect to forward the request to Solr.
This seems to be working on Chrome but not on Safari/Firefox.
Angular ajax request
var solrUrl = '/solr';
storesFactory.getAdvancedMessages = function (searchCriteria, searchType) {
var filterQuery = solrQueryComposer(searchCriteria);
$log.warn(filterQuery);
return $http({
method: 'GET',
url: solrUrl,
params: { 'q': '*',
'fq': filterQuery,
'rows': 30,
'wt': 'json'}
}).
then(function(data, status, headers, config) {
$log.debug(data.data.response.docs);
return data.data.response.docs;
},
function(error){
$log.error(error.message);
});
Play Controller
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Security;
#Security.Authenticated(Secured.class)
public class SolrController extends Controller {
private static String solrUrl = "http://whatever.com:5185/solr/select/";
private static String queryPart = "";
public static Result forward(){
queryPart = request().uri().substring(5);
System.out.println(queryPart);
return seeOther(solrUrl+queryPart);
}
}
Play Route
GET /solr controllers.SolrController.forward()
First of all, I'd like to clarify what you're doing.
Play is not forwarding anything here, it's sending a redirect to the client, asking to fetch another URL. The client will send a request, receive a redirect, and send another request.
Which means:
this controller is not "forwarding" anything. It's just tells the client to go somewhere else. ("seeOther", the name speaks for itself).
It's not secure at all. Anyone knowing solr's URL could just query it directly.
since the query is performed by the client, it may be stopped by the cross-domain security policy.
Moreover, There's a HUGE race condition waiting to happen in your code. solrUrl and queryPart are static, therefore shared by all threads, therefore shared by all clients!!
There's absolutely no reason for queryPart to be static, and actually, there's absolutely no reason for it to be in this scope. This variable should be defined in the method body.
I'd also like to point out that request().uri().substring(5) is very brittle and is going to break if you change the URL in the route file.
In return seeOther(solrUrl+queryPart), queryPart arguments keys and values should also be URLencoded.