Combining mono with flux - spring-data-mongodb

I have simple task. I want to add to order products.
public Mono<OrderDTO> addProductsToOrder(String orderId, String[] productIds)
{
final List<String> productIdsList = Arrays.asList(productIds);
final Mono<Order> order = orderRepository.findById(orderId)
.switchIfEmpty(orderShouldExists(orderId));
final Flux<BaseProductInfo> products = fromIterable(productIdsList)
.flatMap(productId -> productRepository.findById(productId)
.switchIfEmpty(productShouldExists(productId)));
return order.then(combineLatest(order, products, Tuples::of)
.map(objects -> objects.getT1().addProduct(objects.getT2()))
.take(productIdsList.size())
.last()
.flatMap(orderRepository::save)
.map(orderMapper::mapToDTO));
}
What I also want to achive is to return updated order. Code below doesn't work as I would like to. Sometimes all products are saved, some time none of them. The database I am using is reactive mongo.

I solved problem with such solution.
public Mono<OrderDTO> addProductsToOrder(String orderId, String[] productIds)
{
return orderRepository.findById(orderId)
.switchIfEmpty(orderShouldExists(orderId))
.flatMap(order -> Flux
.fromArray(productIds)
.flatMap(productId -> productRepository.findById(productId)
.switchIfEmpty(productShouldExists(productId))
)
.doOnNext(order::addProduct)
.then(Mono.just(order))
)
.flatMap(orderRepository::save)
.map(orderMapper::mapToDTO);
}

From your code, it looks like you are trying to add a list of BaseProductInfo to the Order and save. You can achieve this by doing something like:
public Mono<OrderDTO> addProductsToOrder(String orderId, String[] productIds) {
final List<String> productIdsList = Arrays.asList(productIds);
final Mono<Order> order = orderRepository.findById(orderId)
.switchIfEmpty(orderShouldExists(orderId));
final Mono<List<BaseProductInfo>> products = fromIterable(productIdsList)
.flatMap(productId -> productRepository.findById(productId)
.switchIfEmpty(productShouldExists(productId)))
.collectList();
return order.zipWith(products).map(tuple -> tuple.getT1().addProducts(tuple.getT2)).flatMap(orderRepository::save)
.map(orderMapper::mapToDTO));
}

Related

Dapper One to Many Mapping Logic

The dapper tutorial gives this example to help a user with Multi Mapping (One to Many)
While this works I am curious why they have you store the orders in the dictionary but then in the end they use a linq.Distinct() and return from the list. It seems like it would be cleaner to just return the ordersDictionary.Values as the dictionary logic ensures no duplicates.
//Tutorial
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
List<Order> list = connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID")
.Distinct()
.ToList();
return list;
}
//my suggestion
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
//change 1 no need to store into list here
connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID"); //change 2 remove .Distinct().ToList()
return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
}
I'm the author of this tutorial: https://dapper-tutorial.net/query#example-query-multi-mapping-one-to-many
why they have you store the orders in the dictionary
A row is returned for every OrderDetail. So you want to make sure to add the OrderDetail to the existing Order and not create a new one for every OrderDetail. The dictionary is used for performance to check if the Order has been already created or not.
it would be cleaner to just return the ordersDictionary.Values
How will your query return dictionary values?
Of course, if you are in a method such as yours, you can do
var list = orderDictionary.Values;
return list;
But how to make this Connection.Query return dictionary values? An order is returned for every row/OrderDetail, so the order will be returned multiple times.
Outside the Query, your dictionary solution works great and is even a better solution for performance, but if you want to make your Query return the distinct list of orders without using Distinct or some similar method, it's impossible.
EDIT: Answer comment
my suggestion return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
Thank you for your great feedback, it's always appreciated ;)
It would be weird in a tutorial to use what the query returns when there is no relationship but use the dictionary for one to many relationships
// no relationship
var orders = conn.Query<Order>("", ...).Distinct();
// one to many relationship
conn.Query<Order, OrderDetail>("", ...);
var orders = orderDictionary.Values.ToList();
Your solution is better for performance the way you use it, there is no doubt about this. But this is how people usually use the Query method:
var orders = conn.Query("", ...).Distinct();
var activeOrders = orders.Where(x => x.IsActive).ToList();
var inactiveOrders = orders.Where(x => !x.IsActive).ToList();
They use what the Query method returns.
But again, there is nothing wrong with the way you do it, this is even better if you can do it.

Apex - Retrieving Records from a type of Map<SObject, List<SObject>>

I am using a lead map where the first id represents an Account ID and the List resembles a list of leads linked to that account such as: Map<id, List<Id> > leadMap = new Map< id, List<id> >();
My question stands as following: Knowing a Lead's Id how do I get the related Account's Id from the map. My code looks something like this, The problems is on the commented out line.
for (Lead l : leads){
Lead newLead = new Lead(id=l.id);
if (l.Company != null) {
// newLead.Account__c = leadMap.keySet().get(l.id);
leads_to_update.add(newLead);
}
}
You could put all lead id and mapping company id in the trigger then get the company id
Map<string,string> LeadAccountMapping = new Map<string,string>();//key is Lead id ,Company id
for(Lead l:trigger.new)
{
LeadAccountMapping.put(l.id,l.Company);
}
//put the code you want to get the company id
string companyid= LeadAccountMapping.get(l.id);
Let me make sure I understand your problem.
Currently you have a map that uses the Account ID as the key to a value of a List of Lead IDs - So the map is -> List. Correct?
Your goal is to go from Lead ID to the Account ID.
If this is correct, then you are in a bad way, because your current structure requires a very slow, iterative search. The correct code would look like this (replace your commented line with this code):
for( ID actID : leadMap.keySet() ) {
for( ID leadID : leadMap.get( actId ) ) {
if( newLead.id == leadID ) {
newLead.Account__c = actId;
leads_to_update.add(newLead);
break;
}
}
}
I don't like this solution because it requires iterating over a Map and then over each of the lists in each of the values. It is slow.
If this isn't bulkified code, you could do a Select Query and get the Account__c value from the existing Lead by doing:
newLead.Account__c = [ SELECT Account__c FROM Lead WHERE Id = :l.id LIMIT 1];
However, this relies on your code not looping over this line and hitting a governor limit.
Or you could re-write your code soe that your Map is actually:
Map<ID, List<Leads>> leadMap = Map<ID, List<Leads>>();
Then in your query where you build the map you ensure that your Lead also includes the Account__c field.
Any of these options should work, it all depends on how this code snippet in being executed and where.
Good luck!

XDocument - get list of node values

I have a XDocument something like this:
<LookupList>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_test</FieldName>
<LookupText>open</LookupText>
</ImageInfo1>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_test</FieldName>
<LookupText>Waiting for input</LookupText>
</ImageInfo1>
<ImageInfo1>
<FieldID>1057</FieldID>
<FieldName>Lookup_NEW_NAME</FieldName>
<LookupText>Closed</LookupText>
</ImageInfo1>
</LookupList>
I want to get
number and values of unique FieldName(s) and
a list of LookupText for each FieldName.
Can someone give me a hint how to go about it?
First you need to know how to read the elements from your XDocument:
public void ReadXDocument(XDocument doc)
{
foreach (XElement el in doc.Descendants("ImageInfo1"))
{
string fieldid = el.Element("FieldID").Value;
string fieldName = el.Element("FieldName").Value;
string lookupText = el.Element("LookupText").Value;
}
Once you know that, using Linq to achieve your goal is relatively straight forward.
This should give you a list of distinct FieldNames:
List<String> distinctNames = doc.Descendants("ImageInfo1")
.Select(o => o.Element("FieldName").Value)
.Distinct().ToList();
And this should give you a collection of LookupText values for each FieldName
IEnumerable groups = doc.Descendants("ImageInfo1")
.GroupBy(o => o.Element("FieldName").Value)
.Select(o => new { Key = o.Key, Lookups = o.ToList() });
}

RIA-Services - how to WhereOr or use an IN style construct

I am using SL 4, WCF RIA Services against Entity Framework 4.0. I have an Entity, Visit, that has a string Status field. I have a search screen where I need to display results that have StatusA or StatusB. I am struggling to find a way to specify a client-side query that specifies a collection of statuses that should be matched. If I was to write what I want in SQL it would look something like:
select * from Visit where Status in ('StatusA', 'StatusB');
Client side, it appears to be straightforward to chain Where methods for a WhereAnd effect:
var query = this.PqContext.GetVisitsQuery();
if (!string.IsNullOrEmpty(this.PracticeName))
{
query = query.Where(v => v.PracticeName.ToUpper().Contains(this.PracticeName.ToUpper()));
}
if (this.VisitDateAfter.HasValue)
{
query = query.Where(v => v.VisitDate > this.VisitDateAfter);
}
if (this.VisitDateBefore.HasValue)
{
query = query.Where(v => v.VisitDate < this.VisitDateBefore);
}
However, I can't seem to find a straightforward way to do a WhereOr style operation. I have tried this:
var statuses = new List<string>();
if (this.ShowStatusA)
{
statuses.Add("StatusA");
}
if (this.ShowStatusB)
{
statuses.Add("StatusB");
}
if (statuses.Any())
{
query = query.Where(BuildContainsExpression<Visit, string>(e => e.Status, statuses));
}
Where BuildContainsExpression looks like:
private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == valueSelector)
{
throw new ArgumentNullException("valueSelector");
}
if (null == values)
{
throw new ArgumentNullException("values");
}
ParameterExpression p = valueSelector.Parameters.Single();
if (!values.Any())
{
return e => false;
}
var equals =
values.Select(
value =>
(Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>(Expression.Or);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
But this throws a "Bitwise operators are not supported in queries." exception. Any clues? Is there an alternative way to build an expression tree that works here or do I need to pass all the parameters over to the server and use the BuildContainsExpression there?
Your time and your guidance are much appreciated.
You can create a query method such as the following in your domain service:
GetVisitsByStatus(string[] statusList) {
// create the LINQ where clause here
}
And then from the client, call context.GetVistsByStatusQuery(string[]).
Not all of LINQ is (or even can) be exposed over the URL, so there are always cases where you need to use simple parameters, and have the middle tier construct the LINQ expressions that eventually define the query that goes to the back-end data store.
Hope that helps.

LINQ to SQL for self-referencing tables?

I have a self referencing Categories table. Each Category has a CategoryID, ParentCategoryID, CategoryName, etc. And each category can have any number of sub categories, and each of those sub categories can have any number of sub categories, and so and and so forth. So basically the tree can be X levels deep.
Then Products are associated to leaf (sub) Categories. Is there a way to get all the Products for any given Category (which would be all the products associated to all its leaf descendants) using LINQ to SQL?
This feels like a recursive problem. Is it better to used a Stored Procedure instead?
I don't think linq-to-sql has a good answer to this problem. Since you are using sql server 2005 you can use CTEs to do hierarchical queries. Either a stored procedure or an inline query (using DataContext.ExecuteQuery) will do the trick.
Well here is a terrible rushed implementation using LINQ.
Don't use this :-)
public IQueryable GetCategories(Category parent)
{
var cats = (parent.Categories);
foreach (Category c in cats )
{
cats = cats .Concat(GetCategories(c));
}
return a;
}
The performant approach is to create an insert/modify/delete trigger which maintains an entirely different table which contains node-ancestor pairs for all ancestors of all nodes. This way, the lookup is O(N).
To use it for getting all products belonging to a node and all of its descendants, you can just select all category nodes which have your target node as an ancestor. After this, you simply select any products belonging to any of these categories.
The way I handle this is by using some extension methods (filters). I've written up some sample code from a project I have implemented this on. Look specifically at the lines where I'm populating a ParentPartner object and a SubPartners List.
public IQueryable<Partner> GetPartners()
{
return from p in db.Partners
select new Partner
{
PartnerId = p.PartnerId,
CompanyName = p.CompanyName,
Address1 = p.Address1,
Address2 = p.Address2,
Website = p.Website,
City = p.City,
State = p.State,
County = p.County,
Country = p.Country,
Zip = p.Zip,
ParentPartner = GetPartners().WithPartnerId(p.ParentPartnerId).ToList().SingleOrDefault(),
SubPartners = GetPartners().WithParentPartnerId(p.PartnerId).ToList()
};
}
public static IQueryable<Partner> WithPartnerId(this IQueryable<Partner> qry, int? partnerId)
{
return from t in qry
where t.PartnerId == partnerId
select t;
}
public static IQueryable<Partner> WithParentPartnerId(this IQueryable<Partner> qry, int? parentPartnerId)
{
return from p in qry
where p.ParentPartner.PartnerId == parentPartnerId
select p;
}

Resources