SELECT all with children - sql

I have the following classes:
public class Order
{
public Order()
{
OrderItems = new List<OrderItem>();
}
public int Id { get; set; }
public string OrderName { get; set; }
public int OrderStatusId { get; set; }
public OrderStatus Status { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public string ProductName { get; set; }
}
public class OrderStatus
{
public int Id { get; set; }
public string Description { get; set; }
}
For returning a single order I use this method (any comment on it would also be helpful):
Order GetOrder()
{
using (var con = GetConnection())
{
try
{
con.Open();
string query =
#"SELECT * FROM [Order] JOIN OrderStatus ON [Order].OrderStatusId = OrderStatus.Id WHERE [Order].Id =1;
SELECT * FROM OrderItem Where OrderId = 1";
using (var multi = con.QueryMultiple(query))
{
Order order = multi.Read<Order, OrderStatus, Order>
((ord, ordStat) => { ord.Status = ordStat; return ord; })
.FirstOrDefault();
order.OrderItems = multi.Read<OrderItem>().ToList();
return order;
}
}
catch (Exception ex)
{
return null;
}
}
}
I know I can select all of my orders Id values into a collection on a separate query and run the above method in a foreach loop using the collection values as a parameter but I wonder if dapper has a more elegant solution for returning all of my orders instead of just one.

It sounds like you have a list or array of the ids; dapper supports parameter expansion, allowing use with in, for example:
int[] ids = ...
string query =
#"SELECT * FROM [Order]
JOIN OrderStatus ON [Order].OrderStatusId = OrderStatus.Id
WHERE [Order].Id in #ids;
SELECT * FROM OrderItem Where OrderId in #ids";
using (var multi = con.QueryMultiple(query, new { ids }))
{
var orders = multi.Read<Order, OrderStatus, Order>
((ord, ordStat) => { ord.Status = ordStat; return ord; })
.AsList();
var orderItems = multi.Read<OrderItem>().ToLookup(x => x.OrderId);
foreach(var order in orders) // pick out each order's items
order.OrderItems = orderItems[order.Id].ToList();
}
Any use?

Related

LoadDocument by query in RavenDB index

I have the following structure:
public class Order
{
public string Id { get; set; }
public string CustomerId { get; set; }
public decimal Amount { get; set; }
public DateTimeOffset CreatedDate { get; set; }
}
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
}
I want to export all customers (RavenDB Stream) with order turnover and last ordered date.
I do already have an index (Customers_ByTurnover) which outputs this data (map=Orders, reduce by CustomerId). Although this does only list customers which have already ordered something.
I need an index for all Customers and load these details into each row.
Here is the code I want to write (the Query method is pseudo and doesn't really exist):
public class Customers_ByOrders : AbstractIndexCreationTask<Customer, Customers_ByOrders.Result>
{
public class Result
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Turnover { get; set; }
public DateTimeOffset? LastOrderedDate { get; set; }
}
public Customers_ByOrders()
{
Map = items => items.Select(item => new Result()
{
Id = item.Id,
Name = item.Name,
Turnover = Query<Order>().Where(x => x.CustomerId == item.Id).Sum(x => x.Amount),
LastOrderedDate = Query<Order>().Where(x => x.CustomerId == item.Id).Select(x => x.CreatedDate).OrderByDescending(x => x).FirstOrDefault()
});
}
}
How can I solve this issue?
You cannot create a query inside an index, to get the desired info you will have to create a map-reduce index on the Orders collection, group by on CustomerId and in the reduce function apply Sum() on Amount field and order the LastOrderedDate. To get the Name name field you will have to use LoadDocument extension.
public class Customers_ByOrders : AbstractIndexCreationTask<Orders, Customers_ByOrders.Result>
{
public class Result
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Turnover { get; set; }
public DateTimeOffset? LastOrderedDate { get; set; }
}
public Customers_ByOrders()
{
Map = orders => from o in orders
select new Result
{
Id = o.CustomerId,
Turnover = o.Amount,
LastOrderedDate = o.CreatedDate
};
Reduce = results => from result in results
group result by result.Id
into g
select new Result
{
Id = g.Key,
Turnover = g.Sum(x => x.Turnover),
LastOrderedDate = g.OrderByDescending(x => x.LastOrderedDate).Select(x => x.LastOrderedDate).FirstOrDefault()
};
}
}

How to convert my SQL query to a LINQ statement

I am not familiar with linq complex queries, how can I write the following?
Three tables: Library,Batch,Plan
Library: BatchId ProcessingQuantity
Batch: BatchId
Plan: PlanNo Name Quantity ExecState Date
SELECT b.ProductionPlanBatchId,
a.ProductionPlanNo,
a.ProductConfigName,
sum(c.ProcessingQuantity) AS 'ProcessingQuantity',
sum(a.Quantity) AS 'Quantity',
a.ExecState,
round(CONVERT(float,sum(c.ProcessingQuantity))/CONVERT(float,sum(a.Quantity)), 2) AS 'Percent',
a.ProcessingCompletionDate
FROM ProductionPlan a,
ProductionPlan_Batch b,
ProductionLibrary c
WHERE a.ProductionPlanId = b.ProductionPlanId
AND b.ProductionPlanBatchId = c.ProductionPlanBatchId
AND a.ExecState <> 'Deleted'
GROUP BY b.ProductionPlanBatchId,
a.ProductionPlanNo,
a.ProductConfigName,
a.ProcessingCompletionDate,
a.ExecState
HAVING round(Convert(float,sum(c.ProcessingQuantity))/Convert(float,sum(a.Quantity)), 2) < 1
ORDER BY b.ProductionPlanBatchId DESC
According to your description, I suggest you could try to use below linq.
Class:
[Table("ProductionLibrary")]
public partial class ProductionLibrary
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ProductionPlanBatchId { get; set; }
public int? ProcessingQuantity { get; set; }
}
[Table("ProductionPlan")]
public partial class ProductionPlan
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ProductionPlanNo { get; set; }
[StringLength(10)]
public string ProductConfigName { get; set; }
public int? Quantity { get; set; }
[StringLength(10)]
public string ExecState { get; set; }
[StringLength(10)]
public string ProcessingCompletionDate { get; set; }
public int? ProductionPlanId { get; set; }
}
public partial class ProductionPlan_Batch
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ProductionPlanBatchId { get; set; }
public int? ProductionPlanId { get; set; }
}
Linq:
var re = from a in dbcontext.ProductionPlans
from b in dbcontext.ProductionPlan_Batch
from c in dbcontext.ProductionLibraries
where a.ProductionPlanId == b.ProductionPlanId
&& b.ProductionPlanBatchId == c.ProductionPlanBatchId
&& a.ExecState != "Deleted"
select new
{
a.ExecState,
a.ProcessingCompletionDate,
a.ProductConfigName,
a.ProductionPlanId,
a.ProductionPlanNo,
a.Quantity,
b.ProductionPlanBatchId,
bProductionPlanId = b.ProductionPlanId,
c.ProcessingQuantity,
cProductionPlanId = c.ProductionPlanBatchId
}
into p
group p by new {
p.ProductionPlanId,
p.ProductionPlanNo ,
p.ProductConfigName,
p.ProcessingCompletionDate,
p.ExecState,
p.ProductionPlanBatchId
} into grpre
where Math.Round(((decimal)grpre.Sum(x => x.ProcessingQuantity))/((decimal)grpre.Sum(x => x.Quantity)), 2) <1
orderby grpre.Key.ProductionPlanBatchId descending
select new {
grpre.Key.ProductionPlanBatchId,
grpre.Key.ProductionPlanNo,
grpre.Key.ProductConfigName,
ProcessingQuantity = grpre.Sum(x =>x.ProcessingQuantity) ,
Quantity = grpre.Sum(x => x.Quantity),
grpre.Key.ExecState,
Percent = Math.Round(((decimal)grpre.Sum(x => x.ProcessingQuantity)) / ((decimal)grpre.Sum(x => x.Quantity)), 2),
grpre.Key.ProcessingCompletionDate
};

How to create a search index by related document field in RavenDb?

I think my scenario is a common scenario in any application. I will exemplify using an common used domain: Products and Orders with Items (of products).
What I am trying to do is search orders by the product name, considering that the order has a list of itens and each item has a related productId.
I did some searches and read the Raven documentation, but I was unable to find the answer to my problem.
Please consider the code below:
public class Product
{
public string Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
}
public class Order
{
public string Id { get; set; }
public string OrderNumber { get; set; }
public decimal Total { get; set; }
public string Customer { get; set; }
public Item[] Items { get; set; }
}
public class Item
{
public string ProductId { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class Order_ByProductName : AbstractMultiMapIndexCreationTask<Order_ByProductName.Result>
{
public class Result
{
public string ProductId { get; set; }
public string ProductName { get; set; }
public string[] OrdersIds { get; set; }
public string[] OrdersNumbers { get; set; }
}
public Order_ByProductName()
{
AddMap<Product>(products => from product in products
select new
{
ProductId = product.Id,
ProductName = product.ProductName,
OrderId = default(string),
OrderNumber = default(string)
});
AddMap<Order>(orders => from order in orders
group order by order.Items.Select(c => c.ProductId)
into g
select new
{
ProductId = g.Key,
ProductName = default(string),
OrdersIds = g.Select(c => c.Id),
OrdersNumbers = g.Select(c => c.OrderNumber)
});
Reduce = results => from result in results
group result by result.ProductId
into g
select new
{
ProductId = g.Key,
ProductName = g.Select(r => r.ProductName).Where(t => t != null).First(),
OrdersIds = g.Where(r => r.OrdersIds != null).SelectMany(r => r.OrdersIds),
OrdersNumbers = g.Where(r => r.OrdersNumbers != null).SelectMany(r => r.OrdersNumbers)
};
Sort("ProductName", SortOptions.String);
Index(x => x.ProductName, FieldIndexing.Analyzed);
}
}
class Program
{
static void Main(string[] args)
{
var documentStore = new DocumentStore
{
Url = "http://localhost:8080",
DefaultDatabase = "MyDatabase"
};
documentStore.Initialize();
new Order_ByProductName().Execute(documentStore);
using (var session = documentStore.OpenSession())
{
var product1 = new Product() { Price = 100, ProductName = "Phone" };
var product2 = new Product() { Price = 1000, ProductName = "Laptop" };
var product3 = new Product() { Price = 200, ProductName = "Windows Phone" };
session.Store(product1);
session.Store(product2);
session.Store(product3);
session.SaveChanges();
}
using (var session = documentStore.OpenSession())
{
var products = session.Query<Product>().ToList();
var order1 = new Order();
order1.Customer = "Jhon Doe";
order1.OrderNumber = "001";
order1.Items = new Item[] {
new Item { ProductId = products[0].Id, Price = products[0].Price, Quantity = 1 },
new Item { ProductId = products[1].Id, Price = products[1].Price, Quantity = 1 }
};
order1.Total = order1.Items.Sum(c => (c.Quantity * c.Price));
var order2 = new Order();
order2.Customer = "Joan Doe";
order2.OrderNumber = "002";
order2.Items = new Item[] {
new Item { ProductId = products[2].Id, Price = products[2].Price, Quantity = 1 }
};
order2.Total = order2.Items.Sum(c => (c.Quantity * c.Price));
session.Store(order1);
session.Store(order2);
session.SaveChanges();
}
using (var session = documentStore.OpenSession())
{
var results = session
.Query<Order_ByProductName.Result, Order_ByProductName>()
.Where(x => x.ProductName == "Phone")
.ToList();
foreach (var item in results)
{
Console.WriteLine($"{item.ProductName}\t{string.Join(", ", item.OrdersNumbers)}");
}
Console.ReadKey();
}
}
}
I found a related issue here How to index related documents in reverse direction in Ravendb, but this scenario is a bit different. The code above runs if you comment the index creation, but thorws an exception otherwise: "Could not understand query: Variable initializer must be a select query expression".
I need somthing like below SQL but in RavenDb:
select pro.ProductName, ord.OrderNumber from orders ord
inner join items itm on itm.OrderId = ord.Id
inner join products pro on pro.Id = itm.ProductId
where pro.ProductName like '%Phone%';
How to create a search by ProductName and return a list or Orders in RavenDb?
Im using version 3.5(.1)
Consider the main problem as search "entities by related entity property".
Thanks in advance!
Consider storing the product name directly in the order items. That will have two advantages - the order items won't be affected by later product changes, and indexing will be cheaper.
The following code demonstrates both approaches:
public class Product
{
public string Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
}
public class Order
{
public string Id { get; set; }
public string OrderNumber { get; set; }
public decimal Total { get; set; }
public string Customer { get; set; }
public Item[] Items { get; set; }
}
public class Item
{
public string ProductId { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class OrderView
{
public string OrderId { get; set; }
public string OrderNumber { get; set; }
public string[] ProductIds { get; set; }
public string[] ProductNames { get; set; }
}
public class Order_ByItemName : AbstractIndexCreationTask<Order, OrderView>
{
public Order_ByItemName()
{
Map = orders => from order in orders
select new
{
OrderId = order.Id,
OrderNumber = order.OrderNumber,
ProductIds = order.Items.Select(x => x.ProductId).ToArray(),
ProductNames = order.Items.Select(x => x.ProductName).ToArray(),
};
Index(x => x.ProductNames, FieldIndexing.Analyzed);
StoreAllFields(FieldStorage.Yes);
}
}
public class Order_ByProductName : AbstractIndexCreationTask<Order, OrderView>
{
public Order_ByProductName()
{
Map = orders => from order in orders
let products = LoadDocument<Product>(order.Items.Select(x => x.ProductId))
select new
{
OrderId = order.Id,
OrderNumber = order.OrderNumber,
ProductIds = products.Select(x => x.Id).ToArray(),
ProductNames = products.Select(x => x.ProductName).ToArray(),
};
Index(x => x.ProductNames, FieldIndexing.Analyzed);
StoreAllFields(FieldStorage.Yes);
}
}
class Program
{
static void Main(string[] args)
{
var documentStore = new DocumentStore
{
Url = "http://localhost:8080",
DefaultDatabase = "MyDatabase"
};
documentStore.Initialize();
new Order_ByProductName().Execute(documentStore);
new Order_ByItemName().Execute(documentStore);
using (var session = documentStore.OpenSession())
{
var product1 = new Product() { Id = "products/1", Price = 100, ProductName = "Phone" };
var product2 = new Product() { Id = "products/2", Price = 1000, ProductName = "Laptop" };
var product3 = new Product() { Id = "products/3", Price = 200, ProductName = "Windows Phone" };
session.Store(product1);
session.Store(product2);
session.Store(product3);
session.SaveChanges();
}
using (var session = documentStore.OpenSession())
{
var products = session.Query<Product>().ToList();
var order1 = new Order();
order1.Id = "orders/1";
order1.Customer = "Jhon Doe";
order1.OrderNumber = "001";
order1.Items = new Item[] {
new Item { ProductId = products[0].Id, ProductName = products[0].ProductName, Price = products[0].Price, Quantity = 1 },
new Item { ProductId = products[1].Id, ProductName = products[1].ProductName, Price = products[1].Price, Quantity = 1 }
};
order1.Total = order1.Items.Sum(c => (c.Quantity * c.Price));
var order2 = new Order();
order1.Id = "orders/1";
order2.Customer = "Joan Doe";
order2.OrderNumber = "002";
order2.Items = new Item[] {
new Item { ProductId = products[2].Id, ProductName = products[2].ProductName, Price = products[2].Price, Quantity = 1 }
};
order2.Total = order2.Items.Sum(c => (c.Quantity * c.Price));
session.Store(order1);
session.Store(order2);
session.SaveChanges();
}
Thread.Sleep(5000); // wait for indexing
using (var session = documentStore.OpenSession())
{
var itemResults = session
.Query<OrderView, Order_ByItemName>()
.Search(x => x.ProductNames, "Phone")
.ProjectFromIndexFieldsInto<OrderView>()
.ToList();
var results = session
.Query<OrderView, Order_ByProductName>()
.Search(x => x.ProductNames, "Phone")
.ProjectFromIndexFieldsInto<OrderView>()
.ToList();
Console.WriteLine("Order_ByItemName");
foreach (var order in itemResults)
{
Console.WriteLine($"OrderNumber: {order.OrderNumber}");
for (int i = 0; i < order.ProductIds.Length; i++)
{
Console.WriteLine($"Item: {order.ProductIds[i]} - {order.ProductNames[i]}");
}
}
Console.WriteLine("Order_ByProductName");
foreach (var order in results)
{
Console.WriteLine($"OrderNumber: {order.OrderNumber}");
for (int i = 0; i < order.ProductIds.Length; i++)
{
Console.WriteLine($"Item: {order.ProductIds[i]} - {order.ProductNames[i]}");
}
}
Console.ReadKey();
}
}
}

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.

Why do I get this error in a RavenDB index query: The field 'CustomerId' is not indexed

This is my index code:
public class InvoiceSummaryView
{
public DateTime DueDate { get; set; }
public string CompanyAddress { get; set; }
public string DebtorName { get; set; }
public float Amount { get; set; }
public bool IsPaid { get; set; }
public string CustomerId { get; set; }
}
public class InvoiceSummaryIndex : AbstractIndexCreationTask<CustomerInvoice>
{
public InvoiceSummaryIndex()
{
Map = invoices => from invoice in invoices
select new { DueDate = invoice.DueDate, DebtorId = invoice.DebtorId, Amount = invoice.Amount };
TransformResults = (database, results) =>
from invoice in results
let debtor = database.Load<Debtor>(invoice.DebtorId)
let company = database.Load<Company>(debtor.CompanyId)
select new {
DueDate = invoice.DueDate,
CompanyAddress = Company.Address.ToString(),
DebtorName = debtor.Contact.First + " " + debtor.Contact.Last,
Amount = invoice.Amount,
IsPaid = invoice.IsPaid,
CustomerId = Company.CustomerId
};
}
}
And this is my query:
var query = from viewItem in session.Query<InvoiceSummaryView>("InvoiceSummaryIndex")
where viewItem.CustomerId == id
orderby viewItem.DueDate
select viewItem;
the error is:
"Error": "System.ArgumentException: The field 'CustomerId' is not indexed,
cannot query on fields that are not indexed at ...
Look at your index:
select new { DueDate = invoice.DueDate, DebtorId = invoice.DebtorId, Amount = invoice.Amount };
The fields that you have indexed are DueDate, DebtorId and Amount, that is it.
If you'll it it like this:
select new { DueDate = invoice.DueDate, DebtorId = invoice.DebtorId, Amount = invoice.Amount, invoice.CustomerId };
It will work