How to handle projections in RavenDB - c#

Given domain model...
public class Entity
{
public int Id { get; set; }
public Category Category { get; set; }
}
public class Category
{
public string Title { get; set; }
}
... I want to project results of a select query to this view model:
public class EntityViewModel
{
public int Id { get; set; }
public string CategoryTitle { get; set; }
}
I have tried the following query:
var viewModel = (from entity in _documentSession.Query<Entity>()
select new EntityViewModel
{
Id = entity.Id,
CategoryTitle = entity.Category.Title
}.ToList();
The result of this is only partially correct: the Id is set, the CategoryTitle is not. I understand this behaviour is by design, but I suspect there is an API to handle this scenario.
How should such a projection be handled in RavenDB?
Update: I am using build 1.0.573 in embedded mode.
Updated 2: I have forked RavenDB repository, added a failing test to demonstrate this behaviour and created a pull request (#444). Will post more info as I find out.

Looks like it is actually a bug. See pull request #444 for more information.
I will update this answer when this is fixed in a stable release.
Fixed in the current stable release.

Related

EF Core NullReferenceException on Related Navigation Property

I have two related models.
public class Offer
{
public long Id { get; set; }
public string OfferCode { get; set; }
public string Description { get; set; }
// more properties
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
// more properties
public virtual ICollection<Offer> Offers { get; set; }
}
I am trying to have an MVC form with a select HTML element where Offers are grouped and products
and have the Product Names serve as optgroups.
To this end, I have a view model that I intend to populate with the grouped Offers and I have a method
to do just that.
private OfferMessageViewModel PrepareViewModel(OfferMessageViewModel viewModel)
{
var offers = _context.Offers.Include(o => o.Product).ToList()
.GroupBy(o => o.Product.Name).ToList();
foreach (var offerGroup in offers)
{
var optionGroup = new SelectListGroup
{
Name = offerGroup.Key
};
foreach (var offer in offerGroup)
{
viewModel.Offers.Add(
new SelectListItem
{
Value = offer.OfferCode,
Text = offer.Description,
Group = optionGroup
}
);
}
}
return viewModel;
}
The code gets tripped up in the GroupBy clause.
o.Product is null even when o.ProductID has a value in it.
The ToList() call right before the GroupBy is not helping.
I have tried removing the virtual modifiers on the related entities
navigation properties but the error persisted.
Installing the NuGet package Microsoft.EntityFrameworkCore.Proxies and
modifying and configuring it as such
services.AddDbContext<ApplicationDbContext>(options =>
options.UseLazyLoadingProxies()
.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
also did not make the error go away.
Is there something else I am missing?
Any help would be greatly appreciated.
EDIT:
It has been suggested that my post might be solved by this SO question. But I get the null reference exception even with lazy loading explicitly turned on.
I have tried the suggested solutions there but still no luck.
I eventually solved it.
Apparently the problem was that the foreign key was an int referencing a primary key of type long.
So I changed
public int ProductId { get; set; }
to
public long ProductId { get; set; }
in the Offer model.
Added the necessary migration, updated the database and now it works.
No more null reference exceptions.
Don't know why I missed that but it's probably a combination of lack of sleep and
a not-so-helpful error message throwing me off in a completely different direction.

Web Api, Update DB, connection reset 200ok

I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies
private readonly BackendContext _context;
public CompaniesController(BackendContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
return _context.Companies;
}
I get
I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?
Additional code:
Context
public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
{
public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vendor> Vendors { get; set; }
public DbSet<VendorToProduct> VendorToProducts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
}
Model
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
UPDATE
I've added some values to the table and I got the response of the first company:
[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}
BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key.
UPDATE
Error:
Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:
I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices
While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.
Project your result into an explicitly defined shape and return only what you want to return:
var result = _context.Companies
.Select(c => new
{
c.ID,
c.Name,
c.Description,
c.CustomerID
})
.ToList();
This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.
There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.
As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:
public class CompanyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
}
and returning that:
return _context.Companies
.Select(c => new CompanyViewModel
{
ID = c.ID,
Name = c.Name,
Description = c.Description,
CustomID = c.CustomerID
})
.ToList();
But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.
Another option when that's the case could be to universally set your JSON serialization to ignore circular references:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore );
But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.

C# EntityFramework .NETCore 2.0 Many-to-Many

.. Hello I know there's been already plenty of questions on this, but still can't get it right no matter what.
I need a many-to-many with custom join table, but every time I try to fetch collection it's still empty.
First class:
public class Test1 {
[Key]
public int id { get; set; }
public virtual ICollection<TestToTest> others { get; set; }
public Test1() {
others = new HashSet<TestToTest>();
}
}
Second one:
public class Test2 {
[Key]
public int id { get; set; }
public virtual ICollection<TestToTest> others { get; set; }
public Test2() {
others = new HashSet<TestToTest>();
}
}
And the join table:
public class TestToTest {
[Key]
public int id { get; set; }
[ForeignKey("Test1")]
public int test1Id { get; set; }
[ForeignKey("Test2")]
public int test2Id { get; set; }
public virtual Test1 test1 { get; set; }
public virtual Test2 test2 { get; set; }
}
But still when I try to get one of them with query like:
var cont = new MyContext(); //DbContext
Test1 t1 = cont.test1.Find(1); // Fetches first class ok
var tt = t1.others; // Empty array
I really have no idea what more I'm missing there in other to make it work.
If I add a new one to context then it's ok... as long as it's cached -> and it does write row into db. But after restart (without any cache in context), field 'others' is always empty.
Thanks for any help in advance.
It's not loading as child relationship are setup for Eager Loading. Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Eager loading is achieved using the Include() method. So if the relationship entity is not loaded using include then it would NOT be loaded.
Change the code as
Test1 t1 = cont.test1.Include(t => t.others).SingleOrDefault(t => t.id == 1);
You can read about Eager Loading at this Microsoft document.

Entity Framework Core 2.0 How to automatically Insert Update Delete an detached entity

I have found a lot of threads discussing this topic but none of them have a good solution or I'm just not able to use better search tags :|
I have a Angular2 Frontend with a ASP.Net Core WebApi2 Backend. The database is connected via Entity Framework Core 2.
My issue is just focussed on the backend with EF Core.
My aim is to have a simple method which takes a detached entity (coming from the frontend) which should be updated to the database.
The entity itself has some properties with a lot of navigation properties and these navigation properties have also a lot of navigation properties.
The database model could look like this:
public partial class Project
{
public Project()
{
DocumentNavigation = new HashSet<Document>();
}
public string ProjectNr { get; set; }
public string ProjectTitle { get; set; }
public ICollection<Document> DocumentNavigation { get; set; }
}
public partial class Document
{
public Document()
{
UserNavigation = new HashSet<User>();
}
public int DocumentId { get; set; }
public string DocumentName { get; set; }
public ICollection<User> UserNavigation { get; set; }
}
public partial class User
{
public int UserId { get; set; }
public string UserName { get; set; }
}
My method looks like this:
[HttpPut]
[Route("{projectNr}")]
public IActionResult UpdateProject(string projectNr, [FromBody]Project project)
{
using (abcContext ctx = new abcContext())
{
DoSomething();
ctx.SaveChanges();
}
return NoContent();
}
How do I have to write the method "DoSomething()" in order to deal with this problem?
Imagine, my new object "project" could have new documents, some documents which are still in the database should be deleted because they are no longer in the parameter object "project" and some documents have just changed its normal propoerties like "DocumentName". This could also has happend to the underlaying UserNavigations ...
I just wanted to keep this sample simple. My original entities and the database is much bigger.
To my mind the EF should have a method like "ctx.Update(project)" which manages all these problems???
I cannot believe that I have to do all this stuff manually.
But just in case of doing it manually, how would it look like?
Thanks very much for your support.

Creating a Blog Comments and Reply section using ASP.NET MVC 4 (nested collections)

I'm building a Blog Comment and Reply section and I have these three classes mapped to my DB. The first class holds a collection of related comments to an article, the second class holds a collection of related remarks to the comments:
public class Article
{
public int ArticleID { get; set; }
public byte[] Image { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DatePublished { get; set; }
public string Author { get; set; }
public CategoryTyp Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public int UserID { get; set; }
public string Description { get; set; }
public DateTime CommentDate { get; set; }
public virtual ICollection<Remark> Remarks { get; set; }
}
public class Remark
{
public int RemarkID { get; set; }
public int CommentID { get; set; }
public int ArticleID { get; set; }
public string RemarkDetail { get; set; }
public DateTime RemarkTime { get; set; }
}
And inside my Controller:
public ActionResult GetArticle(int id)
{
var article = db.Articles.Include("Comments").Where(a => a.ArticleID == id).SingleOrDefault();
return View(article);
}
I understand the basis of eager loading but my questions are:
How do you implement it when you're pulling data from multiple related tables?
What is the best practice of populating it to the View? Once I create a View Model how do I stuff the related collections?
1) With multiple related tables you can have two scenarios:
a) Multiple top level relations: you simply add multiple Include statements (I would suggest using lambda expressions instead of strings for this, to avoid typos).
db.Articles
.Include(a=>a.Comments)
.Include(a=>a.SomethingElse)
.FirstOrDefault(a=>ArticleID==id); // Side note: I would suggest this instead of your Where plus SingleOrDefault
For these scenarios I always use a helper method like this one.
b) Multiple nested related entities:
db.Articles
.Include(a=>a.Comments.Select(c=>c.Remarks)
.FirstOrDefault(a=>ArticleID==id);
2) It's a bit up to you how you pass the data to the views. One best practice I can tell you is that you shouldn't let views lazy load any dependant entities or collections. So your use of Include is correct, but I would even suggest to remove the virtual (deactivate lazy loading) to avoid missing an Include by accident.
Regarding the ViewModels you mention, you are actually not using view models, but your data models. This is OK in most cases, unless you need to format the data somehow or add extra information. Then you would need to create a View Model and map it from the data coming from EF.
Another scenario would be if you used WebAPI or an Ajax Action. In that case, I would suggest to use a DTO (equivalent to a ViewModel) to be able to better control the data returned and its serialization.
One last comment about ViewModels is that if you have heavy entities but you only need a few properties, a good choice is to use Projections, to instruct EF to only load the required properties, instead of the full object.
db.Articles
.Include(a=>a.Comments)
.Select(a=>new ArticleDto { Id = a.ArticleID, Title = a.Title })
.ToListAsync();
This will translate to a "SELECT ArticleID, Title FROM Articles", avoiding returning the article bodies and other stuff that you might not need.
You can chain the relationships with Include. For example:
var article = db.Articles.Include("Comments.Remarks").Where(a => a.ArticleID == id).SingleOrDefault();
I'm not sure what you mean by your second question, though. By issuing this query you already have all the comments and all the remarks for those comments. Therefore, you can access them off of the article instance out of the box:
foreach (var comment in article.Comments)
{
...
foreach (var remark in comment.Remarks)
{
...
}
}
How you handle that with your view model is entirely up to you. You could map the comments/remarks to view models of their own, set them directly on the view model, etc. That's all down to what the needs of your application are, and no one but you can speak to that.

Categories

Resources