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

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.

Related

entity framework join 2 tables without modifying domain models

hey new to entity framework and i have these 2 models
SynchronizationProfile
public class SynchronizationProfileDBEntity
{
public string SyncProfileId { get; set; }
public string Code { get; set; }
public SynchronizationType SyncType { get; set; }
public string Schedule { get; set; }
public string UserName { get; set; }
}
SynchronizationLog
public class SynchronizationLogDBEntity
{
public string SyncLogId { get; set; }
public string SyncProfileId { get; set; }
public DateTime Ts { get; set; }
public SyncLogStatus LogStatus { get; set; }
}
there are no foreign keys on these because my team doesnt do it and get the results directly with LINQ
i want to get everything from SynchronizationLog(table) + Code(which is the name for everySyncProfileId) property from SynchronizationProfile
So i tried left join but for some reason dont get me the results.
var profiles = (from item in context.SynchronizationLogs
join item2 in context.SynchronizationProfiles
on item.SyncProfileId equals item2.SyncProfileId into gj
from subitem in gj.DefaultIfEmpty()
select new
{
SyncLogId = item.SyncLogId,
SyncProfileId = item.SyncProfileId,
Ts = item.Ts,
LogStatus = item.LogStatus
Code = item2.Code
}
Is my approach wrong?

Handling queries over related documents in RavenDB

I have a project where I have a set of forms:
public class Form
{
public string Id { get; set; }
public string Name { get; set; }
public IList<string> FieldValueIds { get; set; }
public string UserId { get; set; } // the user who completed the form.
public string FormTemplateId { get; set; }
}
Which each "implement" a form template selected at creation of the form.
public class FormTemplate
{
public string Id { get; set; }
public string Name { get; set; }
public IList<string> FieldIds { get; set; }
}
Which defines which fields are present within the form. Each field
public class FormField
{
public string Id { get; set; }
public string Name { get; set; }
public string Caption { get; set; }
public ValueType DataType { get; set; } // Enum specifying the type of data this field accepts.
}
Stores information about the field such as a description and what type it is expecting. Each FormField can be present in multiple FormTemplates with the values for the form being stored as FieldValue objects related to the Form itself.
public class FieldValue
{
public string Id { get; set; }
public string FieldId { get; set; }
public string ValueAsJsonString { get; set; }
}
Other objects include the User Object:
public class User
{
public string Id { get; set; }
public string Username { get; set; }
public string GivenNames { get; set; }
public string Surname { get; set; }
}
I would like to be able to perform a query to find all Forms completed by a user with a specified name, or all Forms where a field with name X has value Y and so forth.
I have looked into usage of indexes as specified in the documentation Indexing related documents, however the implementation as presented in the documentation threw a NotSupportedException when I implemented the example as follows:
class FormTemplates_ByFieldAndName : AbstractIndexCreationTask<FormTemplate>
{
public class Result
{
public string Name { get; set; }
public IList<string> FieldNames { get; set; }
}
public FormTemplates_ByFieldAndName()
{
Map = FormTemplates => from FormTemplate in FormTemplates
select new
{
Name = FormTemplate.Name,
FieldNames = FormTemplate.FieldIds.Select(x => LoadDocument<FormField>(x).Name)
};
}
}
// in code:
IList<FormTemplate> TestResults = session.Query<FormTemplates_ByFieldAndName.Result, FormTemplates_ByFieldAndName>()
.Where(x => x.Name == "TemplateName" || x.FieldNames.Contains("FieldName"))
.OfType<FormTemplate>()
.ToList();
As best as I can tell this was implemented correctly, however I have seen a suggestion to replace the .Contains with a .Any implementation instead. In lieu of this I have been experimenting with a different approach by applying successive .Where arguments. Like so:
var pre = session.Query<FormTemplates_ByFieldAndName.Result, FormTemplates_ByFieldAndName>();
var pr2 = pre.Where(x => x.Name == "TypeTest25");
List<FormTemplate> TestResults = pr2
.Where(x => x.FieldNames.Any(a => a == "field25"))
.OfType<FormTemplate>()
.OrderByScoreDescending()
.ToList();
Modifying the system to perform in a more factory oriented approach by applying successive filters based on a supplied string in a pre-specified format.
Is this the way I should be going for this implementation and if not what should I be changing? In particular if I am to proceed with the Indexing option how would I apply this technique to the nested relationship between Forms and FormFields through FormTemplates.
You seems to be trying to do this in a way that is mostly relational, but you don't have to.
Instead of trying to have a set of independent documents that each has part of the data, just store it all in a single document.
public class Form
{
public string Id { get; set; }
public string Name { get; set; }
public IList<FieldValue> FieldValues { get; set; }
public string UserId { get; set; } // the user who completed the form.
public string FormTemplateId { get; set; }
}
public class FieldValue
{
public string Id { get; set; }
// can store the value directly!
//public string ValueAsJsonString { get; set; }
public object Value {get; set; }
}
This will generate documents that looks like this:
{
"Id": "forms/1234",
"Name": "Tom",
"FieldValues": [
{
"Id": "FromValues/SchoolDistrictName",
"Value": "ABi195"
}
],
"UserId": "users/tom",
"FormTemplateId": "FromTemplate/1234"
}
Which is a much more natural way to model things.
At that point, you can use RavenDB's ability to index dynamic data, see the docs here:
https://ravendb.net/docs/article-page/3.5/Csharp/indexes/using-dynamic-fields

saving reference using ServiceStack ORMLite

I am using ORMLite as my ORM and I am using it with following structure which contains the foreign key relation ship:
public class Order
{
[AutoIncrement]
public int Id { get; set; }
[Reference]
public Item Item { get; set; }
public string ProUserId { get; set; }
public string Details { get; set; }
}
public class Item
{
[AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
}
As we can see that Order contains the reference to the Item. In DB Order table has a foreign key called ItemId in the table and I have annotated that key in the design view with [Reference] attribute.
I am trying to save the Order with following code:
var order = new Order
{
Item = new Item
{
Id = 3,
Description = "Something"
},
ProUserId = "kunal#kunal.com",
Details = "fdfsdfsd"
};
Db.Save(order,references:true);
I was hoping that ORMLite would pick up the relationship and with ItemID in the Order table but it did not and it did throw following error instead:
Cannot insert the value NULL into column 'ItemId', table 'WebApp.dbo.Order'; column does not allow nulls. INSERT fails.
I tried changing my schema and addred OrderId column in my Item table with reference there and that works fine. But that is not the correct design. Should I make any changes in my code/schema to support this feature?
You still need to provide the foreign key that OrmLite can use to store the relationship, e.g. either on the Child/ForeignKey table:
public class Order
{
[AutoIncrement]
public int Id { get; set; }
[Reference]
public Item Item { get; set; }
public string ProUserId { get; set; }
public string Details { get; set; }
}
public class Item
{
[AutoIncrement]
public int Id { get; set; }
public int OrderId { get; set; } //Parent Table PK
public string Description { get; set; }
}
Or for 1:1 relationships, can be on the Parent table, e.g:
public class Order
{
[AutoIncrement]
public int Id { get; set; }
[Reference]
public Item Item { get; set; }
public int ItemId { get; set; } //Child Table PK
public string ProUserId { get; set; }
public string Details { get; set; }
}
public class Item
{
[AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
}

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

RavenDB TransformResults

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.