RavenDB TransformResults - ravendb

I'm trying to use the TransformResults feature, and I can't get it to work. I'm not totally sure I understand this feature, perhaps there is another way to solve this problem. What I want is just the Id from the Order and the email addesses from the Customer and the Entrepreneur. I am happy for all tips that can take me in the right direction. Here is my code.
Document
public class OrderDocument
public string Id {get; set }
public EntrepreneurInfo EntrepreneurInfo { get; set; }
public CustomerInfo CustomerInfo { get; set; }
public OrderStatus CurrentOrderStatus { get; set; }
}
Info classes
public class EntrepreneurInfo
{
public string EntrepreneurDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public class CustomerInfo
{
public string CustomerDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
The info classes are just subsets of a Customer and Entrepreneur documents respectively.
The Customer and Entrepreneur documents inherits from a base class ( AbstractOrganizationDocument) that has the EmailAddress property.
My Index
public class OrdersApprovedBroadcastingData :
AbstractIndexCreationTask<OrderDocument, OrdersApprovedBroadcastingData.ReduceResult>
{
public OrdersApprovedBroadcastingData()
{
this.Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
Id = d.Id,
CustomerId = d.CustomerInfo.CustomerDocumentId,
EntrepreneurId = d.EntrepreneurInfo.EntrepreneurDocumentId
};
this.TransformResults = (db, orders) => from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurId)
select
new
{
o.Id,
o.CustomerId,
CustomerEmail = customer.EmailAddress,
o.EntrepreneurId,
EntrepreneurEmail = entrepreneur.EmailAddress
};
}
public class ReduceResult
{
public string Id { get; set; }
public string CustomerId { get; set; }
public string CustomerEmail { get; set; }
public string EntrepreneurId { get; set; }
public string EntrepreneurEmail { get; set; }
}
}
If I look at the result of this Index in Raven Studio I get null values for all fields except the Id. And finally here is my query.
Query
var items =
this.documentSession.Query<OrdersApprovedBroadcastingData.ReduceResult, OrdersApprovedBroadcastingData>()
.Select(x => new OrdersToBroadcastListItem
{
Id = x.Id,
CustomerEmailAddress = x.CustomerEmail,
EntrepreneurEmailAddress = x.EntrepreneurEmail
}).ToList();

Change your index to:
public class OrdersApprovedBroadcastingData : AbstractIndexCreationTask<OrderDocument>
{
public OrdersApprovedBroadcastingData()
{
Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
};
TransformResults = (db, orders) =>
from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerInfo.CustomerDocumentId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurInfo.EntrepreneurDocumentId)
select new
{
o.Id,
CustomerEmailAddress = customer.EmailAddress,
EntrepreneurEmailAddress = entrepreneur.EmailAddress
};
}
}
Your result class can simply be the final form of the projection, you don't need the intermediate step:
public class Result
{
public string Id { get; set; }
public string CustomerEmailAddress { get; set; }
public string EntrepreneurEmailAddress { get; set; }
}
You don't have to nest this class in the index if you don't want to. It doesn't matter either way. You can query either with:
var items = session.Query<Result, OrdersApprovedBroadcastingData>();
Or with
var items = session.Query<OrderDocument, OrdersApprovedBroadcastingData>().As<Result>();
Though, with the first way, the convention tends to be to nest the result class, so really it would be
var items = session.Query<OrderDocument.Result, OrdersApprovedBroadcastingData>();
Note in the index map, I am not including any properties at all. None are required for what you asked. However, if you want to add a Where or OrderBy clause to your query, any fields you might want to filter or sort on should be put in there.
One last thing - the convention you're using of OrderDocument, CustomerDocument, EntrepreneurDocument, is a bit strange. The usual convention is just Order, Customer, Entrepreneur. Think of your documents as the persisted form of the entities themselves. The convention you are using will work, it's just not the one usually used.

Related

child relationship not available in parent object

I have the following method that returns a list of factory cars.
It works, but the ordering is wrong.
CarEngines can have an orderId and I want to order by that.
Looking at other answers on here, I see that you can't do an order by inside the query and you have to do it afterwards.
The problem is, I can't access CarEngines as you can see below:
public async Task<ActionResult<CountryList>> GetCountryCarObject(Guid countryID)
{
var factoryCars = await _context.CountryList
.Include(n => n.CarList).ThenInclude(l => l.CarEngines)
.Include(n => n.CarList).ThenInclude(l => l.CarOptions)
.SingleOrDefaultAsync(c => c.CountryId == countryID);
factoryCars.CarList.CarEngines <== CarEngines doesn't show up in CarList object
return factoryCars;
}
It is telling me that CarList doesn't contain a definition for CarEngines.
But it is in my CarList model, I have it defined like so:
public CarList()
{
CarEngines = new HashSet<CarEngines>();
}
public virtual ICollection<CarEngines> CarEngines { get; set; }
Here are the two models:
public partial class CarList
{
public CarList()
{
CarEngines = new HashSet<CarEngines>();
CarOptions = new HashSet<CarOptions>();
}
public string CarId { get; set; }
public string CarMake { get; set; }
public string CarModel { get; set; }
public Guid? CarCountryId { get; set; }
public virtual ICollection<CarEngines> CarEngines { get; set; }
public ICollection<CarOptions> CarOptions { get; set; }
}
public partial class CountryList
{
public CountryList()
{
CarList = new HashSet<CarList>();
}
[Key]
public Guid CountryId { get; set; }
public string CountryName { get; set; }
public string CountryLocation { get; set; }
public string CountryDesc { get; set; }
public virtual ICollection<CarList> CarList { get; set; }
}
So I'm not sure it doesn't see it.
Is there a way to get this to work?
Thanks!
Ok so the fact that there is something called CarList but is not a List is super confusing but moving on....
The issue is that CarList is a List. So use something like factoryCars.CarList.Select( x=>x.CarEngines). Also rename that to var country instead of var factoryCars since you return a single country and not a list of cars.
Also rename your variables and classes this confusion was probably caused by this. For example instead of having ICollection<CarList> CarList you can rename it into ICollection<Car> Cars so right now from the name you can easilly understand there are multiple cars (thus its a collection) which includes the object Car

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()
};
}
}

Ravendb TransformResults showing null values for properties populated with Load()

I have two documents Ticket and MenuItem i have created index with TransformResults but problem is i am getting null value for Loaded document in transform
public class Ticket
{
public int ID { get; set; }
public int ItemId { get; set; }
public string ItemName { get; set; }
public int Price { get; set; }
}
public class MenuItem
{
public int ID { get; set; }
public string ItemName { get; set; }
public string PriceCategory { get; set; }
}
i have created a index like
public class TicketItemGross : AbstractIndexCreationTask<Ticket, TicketItemGross.TicketItemDetails>
{
public class TicketItemDetails
{
public string ID { get; set; }
public string ItemId { get; set; }
public string ItemName { get; set; }
public int Price { get; set; }
public string PriceCategory { get; set; }
}
public TicketItemGross()
{
Map = docs => from doc in docs
select new
{
ID = doc.ID,
ItemId=doc.ItemId,
ItemName=doc.ItemName,
Price=doc.Price
};
TransformResults = (database, docs) => from m in docs
let d = database.Load<MenuItem>(m.ID)
select new
{
ID = m.ID,
ItemId = m.ItemId,
ItemName = m.ItemName,
Price = m.Price,
PriceCategory=d.PriceCategory
};
}
}
and the problem is that when i query data. I get null for PriceCategory but for all other fields i get correct value
here is query
IEnumerable<TicketItemGross.TicketItemDetails> list;
using (var session = store.OpenSession())
{
list = session.Query<TicketItemGross.TicketItemDetails, TicketItemGross>();
}
This is happening because you are using integer IDs. When you call database.Load in your transform, you'll need to manually convert it to a string ID.
database.Load<MenuItem>("MenuItems/" + m.ID)
This is one of several places where Raven gets confused if you use integer or guid IDs. If you use string ids, you won't have this problem.
Also, you might consider using a results transformer instead. They are easier than index transformers, which are now obsolete.

RavenDb Select() downcasts instead of selecting the neccessary fields

public class PersonBrief
{
public int Id { get; set; }
public string Picture { get; set; }
public PersonBrief(Person person)
{
Id = person.Id;
Picture = person.Picture;
}
}
public class Person : PersonBrief
{
public string FullName { get; set; }
}
var results = session.Query<Person>()
.Select(x => new PersonBrief(x))
.ToList();
Assert.IsNull(results[0] as Person); // Fails
Is this a bug? If not, what would be the correct way to select only the fields i'm interested in?
It would work if you move the .ToList before the .Select, but that would be doing the work on the client.
If you want to do it on the server, you need to use As in your query, and you need a static index that does a TransformResults. See these docs.

How to use Raven LoadDocument

I'm having trouble querying RavenDB with even the simplest of queries, probably I'm doing something wrong, but after a few hours I just can't see it anymore. I've googled almost anything I can think of..
I have these entities:
public class User
{
public string Id { get; set; }
public string DisplayName { get; set; }
public string RealName { get; set; }
public string Email { get; set; }
public string PictureUri { get; set; }
public List<Comment> Comments { get; set; }
public List<Role> Roles { get; set; }
}
public class NewsItem
{
public string Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public DateTime Created { get; set; }
public string UserId { get; set; }
public List<Tag> Tags { get; set; }
public List<Comment> Comments { get; set; }
public List<WebImage> Images { get; set; }
}
I want to query these so I get a list of newsItems, but with the user information alongside it. So I read the docs and tried the LoadDocument feature, the index:
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result
{
public string AuthorName { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
select new
{
AuthorName = LoadDocument<User>(newsItem.UserId).DisplayName
};
}
}
Which I try to use like:
var result = _documentSession.Query<NewsItemIndexWithComments.Result, NewsItemIndexWithComments>().AsProjection<NewsItemIndexWithComments.Result>().ToList();
Now I get the number of documents in my list, but the AuthorName is always null. If I don't use the AsProjection method, I won't get any results. Can anyone show me a proper example on which I can experiment further?
Thanks.
_ edit:
That helped a lot, thanks :) Now for step two, I'm sorry if I'm being a bit newbish, but you'll have to start somewhere. In the newsitems there are comments, in these comments there is another reference to the userid. You can probably guess what I want to do: I want the user info for the comments with the comments as well.
new Index:
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result : NewsItem
{
public string AuthorName { get; set; }
public string AuthorId { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
let user = LoadDocument<User>(newsItem.UserId)
select new
{
AuthorName = user.DisplayName,
AuthorId = user.Id,
};
Store(x => x.AuthorName, FieldStorage.Yes);
Store(x => x.AuthorId, FieldStorage.Yes);
}
}
Comment class:
public class Comment
{
public string Id { get; set; }
public string Text { get; set; }
public string UserId { get; set; }
public string User { get; set; }
public DateTime Created { get; set; }
}
How can I query the comments and expand the results for that? Or is it better to create a new index just for the comments and get the user info analog to the solution above?
You're almost there, you just need to store the field you are projecting. Add this to the index constructor, after the map.
Store(x=> x.AuthorName, FieldStorage.Yes);
This is because you want it returned and available for AsProjection to find. If you just wanted to use the author name in a where or orderby, you wouldn't need it.
If you just want to include the comments in your AsProjection, you can simply index the entire object along.
Note that indexing a custom object will mean that you're not able to query on it using a .Where(). RavenDB can only query on flattened results (ints, decimals, strings, dates).
In order to, for instance, query on the title, you will need to create a seperate Property public string Title { get; set; } and map it with Title = newsItem.Title.
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result : NewsItem
{
public string AuthorName { get; set; }
public string AuthorId { get; set; }
public List<Comment> Comments { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
let user = LoadDocument<User>(newsItem.UserId)
select new
{
AuthorName = user.DisplayName,
AuthorId = user.Id,
Comments = newsItem.Comments.
};
Store(x => x.AuthorName, FieldStorage.Yes);
Store(x => x.AuthorId, FieldStorage.Yes);
Store(x => x.Comments, FieldStorage.Yes);
}
}