RxJava - retrieving entry in Observable in second flatMapSingle - rx-java2

We have a vertx verticle which receives an id and uses it see if an entity with the id exist in a database. It contains the following logic:
if (itemFound) {
e.onNext(item_which_was_found)
}
else {
e.onNext(null);
}
Another verticle has an Observable which processes a list of id's. It uses rxSend to pass each id in the list to the first verticle to do the database lookup:
Observable<Object> observable = ...
observable.flatMapSingle(id -> {
return rxSend(VERTICLE_1_ADDRESS, id);
})
.flatMapSingle ( i ->
{
// Logic dependent on if item was found
)
.subscribe();
With the above, it is easy to handle cases where the entity associated with the id was found in the database, because the first vertcle, in onNext(), returns the entity. The question is for the second case, when no entity exists and first verticle returns onNext(null). In this case, how is it possible to retrieve, in the second flatMapSingle, the item in the observable which is currently being processed (that is, the id which has no associated database entity) ? Or is there a better way to structure the code?
Thanks

You can change your observable definition to:
Observable<Object> observable = observable();
observable.flatMapSingle(id -> {
return rxSend(VERTICLE_1_ADDRESS, id).flatMap(i -> {
// Logic dependent on if item was found
// id is visible here
});
}).subscribe();
Then the id will be visible to your second lambda.

Related

Update method throws an exception, because the entity is already tracked

I have a problem with EF Core 5 that is really getting me down.
FYI, LazyLoadingProxies are used (something else that just gives me a headache, but well, different topic).
Information for the code below:
Service: A service per entity, contains all CRUD operations into the database and other methods if needed.
Workflow: Uses multiple services at once to perform certain operations (e.g. create product -> create product folder -> save product).
Problem:
I have an entity "Product" which contains the following update method which is used to update the properties of the entity with those of another object:
public override void Update(Product source)
{
// Properties
AnnualPrice = source.AnnualPrice;
...
// Relations
var sourceRelatedProductIds = source.RelatedWithProductIds.Where(x => x != Id);
if (sourceRelatedProductIds.Count() != 0)
{
RelatedWithProducts.Clear();
foreach (var relatedWithProduct in ctx.Set<Product>().Where(x => source.RelatedWithProductIds.Contains(x.Id)).AsNoTracking())
{
RelatedWithProducts.Add(relatedWithProduct);
}
}
var oldShortDescriptions = ShortDescriptions.ToList(); <--- EXCEPTION
ShortDescriptions.Clear();
foreach (var shortDescription in source.ShortDescriptions)
{
shortDescription.Id = oldShortDescriptions.FirstOrDefault(x => x.Culture == shortDescription.Culture)?.Id ?? 0;
ShortDescriptions.Add(shortDescription);
}
...
}
In the line with the arrow and "Exception", I get the following exception:
System.InvalidOperationException: 'The instance of entity type 'Product' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
In itself, I understand what the exception is trying to tell me. My problem is that I can't find the reason for it anywhere. Because as far as I can tell, the product with ID 1 can't be tracked yet.
Of course, the problem is not in the update method, but before it, so here is the rest of the code.
ProductController.Update:
[HttpPut("update")]
public IActionResult Update(C.Product[] products)
{
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState.Values.SelectMany(x => x.Errors));
}
var dbProducts = products.Select(ToDatabase).ToArray(); <--- Just converts the given client model into a Database model
var result = productWorkflow.Update(dbProducts); <--- Calls a workflow class, NOT the update method of the entity
return CoreToActionResultConverter.ToActionResult<Db.Product>(result);
}
ProductWorkflow.Update:
public ResultBase Update(params Product[] products)
{
var result = productService.AddOrUpdate(products); <--- This calls the Service CRUD AddOrUpdate method
if (result is not ServiceResult<Product>)
{
return result;
}
return new ServiceResult<Product>(ResultType.AddedOrUpdated);
}
ProductService.AddOrUpdate:
public virtual ResultBase AddOrUpdate(IEnumerable<TEntity> entities)
{
var currentEntities = new List<TEntity>();
foreach (var entity in entities)
{
var currentEntity = Get(entity.Id); <--- This line is the only one where I could imagine that it is already tracked here. The problem is only that it does not work ONLY with the workflow. If I call my AddOrUpdate method from the controller, which directly calls THIS method, it works (although this line is just executed the same way).
if (currentEntity == null)
{
currentEntity = Ctx.CreateProxy<TEntity>();
Ctx.Attach(currentEntity);
}
if (currentEntity != entity)
{
currentEntity.Update(entity);
}
currentEntities.Add(currentEntity);
}
Ctx.AddRange(currentEntities.Where(x => x.Id == 0));
Ctx.UpdateRange(currentEntities.Where(x => x.Id != 0));
try
{
Ctx.SaveChanges();
}
catch (DbUpdateException ex)
{
// Commented out the error handling to remove unnecessary things for the post
}
return new ServiceResult<TEntity>(ServiceResult.ResultType.AddedOrUpdated, currentEntities);
}
I found the problem and it was not on the line where the exception was thrown, but before.
In my Product.Update() method (the first code snippet), I get the Related Products by ID and add them to the list (Simply a Many to Many relationship, from Product <--> Product). When calling Update, I specified ID 1 in the RelatedProductIds, however the entity itself also has ID 1, so it references itself. I have now just fixed that by omitting the ID, if the same as the object itself.
This still doesn't explain why it works with a breakpoint, because it's still tracked in that case (or not tracked, since I'm using AsNoTracking(), but good).

Correct pattern to insert or update existed entity with spring data reactive

I implement an application with spring data (mongo).
It gets data from external service
Store it to DB
If item does not exist I'd like to insert it to db
If item exists but has same md5 hash with external item - do nothing
If item exists in DB and md5 hashes are not equal I'd like to update it
I have the code
public Mono<Void> processItems() {
return externalService.getItems() //this returns Flux<Item>
.flatMap(this::createOrUpdateItem)
.flatMap(itemRepository::save)
.then()
}
private Mono<Item> createOrUpdateItem(Item item) {
return itemRepository.findById(item.getId) //this returns Mono<Item>
.flatMap(itemFromDb -> updateItem(itemFromDb, item))
.defaultIfEmpty(item)
}
private Mono<Item> updateItem(Item itemFromDb, Item item){
if(itemFromDb.getMd5.equals(item.getMd5)){
return Mono.just(itemFromDb);
}
itemFromDb.setName(item.getName)
.setDescription(item.getDescription);
return Mono.just(itemFromDb);
}
How does it work:
If item does not exist it stored in db
If item exists and has same md5 hash updateItem returns existed in db and version in DB increases
If item exists and md5 hashes are not equal updateItem set feilds and returns existed in db and version in DB increases
The problem is: when md5 hash are equal I don't want to cal DB. Because it increases version of the item in db but there were no changes.
How to properly implement this algorithm?
I can use #EqualsAndHashCode(exclude = "id") but not sure if it is a right way
The only missing ingredient in your example is to continue with an empty stream if nothing needs to be done.
This solution is actually similar to yours, but has a clear separation of concerns.
The first flatMap only loads existing data. The second one contains only the business logic to decide what to do. It's up to you to follow these established principles (Single Responsibility Principle, Integration Operation Segregation Principle etc.).
public Mono<Void> processItems() {
return externalService.getItems() //this returns Flux<Item>
.flatMap(this::loadExistingItem)
.flatMap(this::setupOperation)
.flatMap(this::saveItem)
.then()
}
private Mono<List<Item>> loadExistingItem(Item item) {
return itemRepository.findById(item.getId)
.map(fromDb -> Arrays.asList(item, fromDb))
.defaultIfEmpty(() -> Arrays.asList(item, null));
}
private Mono<Item> setupOperation(List<Item> items) {
Item newItem = items.get(0);
Item existingItem = items.get(1);
if (existingItem == null) {
return Mono.just(newItem);
}
if(newItem.getMd5().equals(existingItem.getMd5())){
return Mono.empty();
}
existingItem.setName(newItem.getName)
existingItem.setDescription(newItem.getDescription);
return Mono.just(existingItem);
}

RxJava - synchronized block - inner Source

I want to use synchronized block for source of flatMap. But I need to use this construct for processing (method processItem), not only when inner source is created.
This Observable is called each 5 minutes (for example Observable.interval)
Observable.fromIterable(getSourceData())
.flatMapCompletable(item -> {
synchronized(LockManager.getInstance().getLockObject(item.id)){
return processItem(item)
.subscribeOn(Schedulers.io());
}
})
My processsItem method looks like:
public Completable processItem(Item item){
return mApiSource.getItemById(item.id)
.flatMap(item ->
mItemRepository.replace(item)
)
.toCompletable();
}
Both inner methods return Single.
It is part of method for periodic updates from server. I need to serialize processItem method call (periodic synchronization of item from server) with methods for modifiction of Item (update, delete) which are called from other classes of project (synchronization for item with ).
Main problem is that periodic update can rewrite newly updated item.
Actually I use this solution:
Chain for updating new item:
public Completable updateItem(Item item){
return Completable.fromAction(() -> {
synchronized(LockManager.getInstance().getLockObject(item.id)){
mApiSource.update(item)
.flatMap(item ->
mItemRepository.replace(item)
)
.toCompletable()
.blockingAwait();
}
})
.subscribeOn(Schedulers.io())
}
Chain for periodic update:
Observable.fromIterable(getSourceData())
.flatMapCompletable(item -> {
Completable.fromAction(() ->{
synchronized(LockManager.getInstance().getLockObject(item.id)){
processItem(item).blockingAwait();
}
})
.subscribeOn(Schedulers.io())
});
I know that is not clear RxJava solution.
Do you know better solution for this problem?

querying in EntityObject

I have partial class TaxReportItem with partial method OnActualVolumeChanging(double value):
public partial class TaxReportItem
{
partial void OnActualVolumeChanging(double value)
{
if (Tax != null)
{
Payment = value*Tax.TaxRate;
}
}
}
In this method i want to get all collection of TaxReportItems that's present in context(something like this):
partial void OnActualVolumeChanging(double value)
{
var sum = 0.0;
if (Tax != null)
{
Payment = value*Tax.TaxRate;
foreach (var taxReportItem in ????)
{
sum += taxReportItem.Sum;
}
}
}
How can i achieve this?
This is actually quite hard because you should never need this. If you need this, design of your entity object is wrong and it is doing something which should be done elsewhere. Single TaxReportItem should never need to know about other tax report items and load them from database unless other items are dependent on this item (they form an aggregate). In such case you should have navigation property to dependent items in the principal one.
To follow your question. If you need to load other items you must have instance of the context to do that. You can either get instance used to load current item and use it to load other entities (bad solution) or you can create a new instance of the context and use it to load other entities (even worse solution).
As you can see from the linked article getting current context instance from the entity itself is not easy and it has some prerequisites which goes back to the first paragraph. It is hard because it is wrong approach.

Entity Framework - Auditing activity

My database has a 'LastModifiedUser' column on every table in which I intend to collect the logged in user from an application who makes a change. I am not talking about the database user so essentially this is just a string on each entity. I would like to find a way to default this for each entity so that other developers don't have to remember to assign it any time they instantiate the entity.
So something like this would occur:
using (EntityContext ctx = new EntityContext())
{
MyEntity foo = new MyEntity();
// Trying to avoid having the following line every time
// a new entity is created/added.
foo.LastModifiedUser = Lookupuser();
ctx.Foos.Addobject(foo);
ctx.SaveChanges();
}
There is a perfect way to accomplish this in EF 4.0 by leveraging ObjectStateManager
First, you need to create a partial class for your ObjectContext and subscribe to
ObjectContext.SavingChanges Event. The best place to subscribe to this event is inside the OnContextCreated Method. This method is called by the context object’s constructor and the constructor overloads which is a partial method with no implementation:
partial void OnContextCreated() {
this.SavingChanges += Context_SavingChanges;
}
Now the actual code that will do the job:
void Context_SavingChanges(object sender, EventArgs e) {
IEnumerable<ObjectStateEntry> objectStateEntries =
from ose
in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified)
where ose.Entity != null
select ose;
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
}
}
}
}
Code Explanation:
This code locates any Added or Modified entries that have a LastModifiedUser property and then updates that property with the value coming from your custom Lookupuser() method.
In the foreach block, the query basically drills into the CurrentValues of each entry. Then, using the Where method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose Name is LastModifiedUser. Next, the if statement verifies that the LastModifiedUser property is a String field; then it updates the field's value.
Another way to hook up this method (instead of subscribing to SavingChanges event) is by overriding the ObjectContext.SaveChanges Method.
By the way, the above code belongs to Julie Lerman from her Programming Entity Framework book.
EDIT for Self Tracking POCO Implementation:
If you have self tracking POCOs then what I would do is that I first change the T4 template to call the OnContextCreated() method. If you look at your ObjectContext.tt file, there is an Initialize() method that is called by all constructors, therefore a good candidate to call our OnContextCreated() method, so all we need to do is to change ObjectContext.tt file like this:
private void Initialize()
{
// Creating proxies requires the use of the ProxyDataContractResolver and
// may allow lazy loading which can expand the loaded graph during serialization.
ContextOptions.ProxyCreationEnabled = false;
ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
// We call our custom method here:
OnContextCreated();
}
And this will cause our OnContextCreated() to be called upon creation of the Context.
Now if you put your POCOs behind the service boundary, then it means that the ModifiedUserName must come with the rest of data from your WCF service consumer. You can either expose this
LastModifiedUser property to them to update or if it stores in another property and you wish to update LastModifiedUser from that property, then you can modify the 2nd code as follows:
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata sourceField = fieldsMetaData
.Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal,
entry.CurrentValues[sourceField.Ordinal].ToString());
}
}
}
Hope this helps.
There is a nuget package for this now : https://www.nuget.org/packages/TrackerEnabledDbContext
Github: https://github.com/bilal-fazlani/tracker-enabled-dbcontext