My data model is:
public class Category
{
public Guid Id { get; set; }
public List<Category> children { get; set; }
}
All I want is to get data from any level of nesting.
I've created an index for that:
public class CategoriesIndex :
AbstractIndexCreationTask<Category, CategoriesIndex.ReduceResult>
{
public class ReduceResult
{
public Guid Id { get; set; }
public Category Category { get; set; }
}
public CategoriesIndex()
{
Map = categories =>
from category in categories
from subcategory in category.Hierarchy(x=>x.children)
select new {
Id = subcategory.Id,
Category = subcategory
};
Store(x => x.Id, FieldStorage.Yes);
Store(x => x.Category, FieldStorage.Yes);
}
}
After running that code i've got an exception
Url: "/indexes/CategoriesIndex"
System.InvalidOperationException: Source code.
What's wrong? And if it is how can I index hierarchial data?
P.S. I can't change data model due to some restrictions
Update
I've got the message of the exception:
public class Index_CategoriesIndex : AbstractViewGenerator
{
public Index_CategoriesIndex()
{
this.ViewText = #"docs.Categories.SelectMany(category => category.Hierarchy(x => x.children), (category, subcategory) => new() {
Id = subcategory.Id,
Category = subcategory
})";
this.ForEntityNames.Add("Categories");
this.AddMapDefinition(docs => docs.Where(__document => __document["#metadata"]["Raven-Entity-Name"] == "Categories").SelectMany((Func<dynamic, IEnumerable<dynamic>>)(category => (IEnumerable<dynamic>)(category.Hierarchy(x => x.children))), (Func<dynamic, dynamic, dynamic>)((category, subcategory) => new { Id = subcategory.Id, Category = subcategory, __document_id = category.__document_id })));
this.AddField("Id");
this.AddField("Category");
this.AddField("__document_id");
}
} e:\RavenDB\Web\Tenants\RavenPortalAuth\IndexDefinitions\TemporaryIndexDefinitionsAsSource\4jy1udpm.0.cs(21,223) :
error CS1977: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type
You should use AbstractIndexCreationTask's Recurse method.
http://ravendb.net/docs/2.0/client-api/querying/static-indexes/indexing-hierarchies
Related
I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value
I am working on a ASP.NET Core Web API project and I want the controllers return results asynchronously. But I am having problems with the get by id call.
Models:
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string ProductType { get; set; }
[Required]
[MaxLength(250)]
public string ProductDescription { get; set; }
[Required]
public int ProductCategory { get; set; }
//Navigation Properties
public int TravelPackageId { get; set; }
public TravelPackage TravelPackage { get; set; }
}
public class TravelPackage
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string PackageName { get; set; }
//Navigation Properties
public List<Product> Products { get; set; }
}
TravelPackage has a list of products. When I make a call to a travelPackage by id, I need that list of products be returned too.
This is my repository for both call (all travelPackages and specific travelPackage by id)
public class TravelPackageRepository : ITravelPackageRepository
{
private readonly DataContext _context;
public TravelPackageRepository(DataContext context)
{
_context = context;
}
public async Task<TravelPackage> GetProductByIdAsync(int id)
{
return await _context.TravelPackages.Include(t => t.Products.Select(p => new
{
Id = t.Id,
PackageName = t.PackageName,
Products = t.Products.Select(product => new Product()
{
Id = product.Id,
ProductType = product.ProductType,
ProductDescription = product.ProductDescription,
ProductCategory = product.ProductCategory,
TravelPackageId = product.TravelPackageId
})
})).FirstAsync(t => t.Id == id);
}
public async Task<System.Collections.Generic.IReadOnlyList<TravelPackage>> GetTravelPackagesAsync()
{
return await _context.TravelPackages
.Include(x => x.Products)
.ThenInclude(x => x.TravelPackage)
.ToListAsync();
}
}
But I am getting the following error:
System.InvalidOperationException: The expression 't.Products.AsQueryable().Select(p => new <>f__AnonymousType2`3(Id = t.Id, PackageName = t.PackageName, Products = t.Products.AsQueryable().Select(product => new Product() { Id = product.Id, ProductType = product.ProductType, ProductDescription = product.ProductDescription, ProductCategory = product.ProductCategory, TravelPackageId = product.TravelPackageId})))' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations.
Before implementing the repository and async/await, this was my controller for travelPackages and it worked fine:
[HttpGet("{id}")]
public ActionResult<TravelPackage> GetTravelPackage(int id)
{
var item = _context.TravelPackages.Where(package => package.Id == id).Select(package => new
{
Id= package.Id,
PackageName =package.PackageName,
Products = package.Products.Select(product => new Product()
{
Id = product.Id,
ProductType = product.ProductType,
ProductDescription = product.ProductDescription,
ProductCategory = product.ProductCategory,
TravelPackageId = product.TravelPackageId
})
});
return Ok(item);
}
I do not have much experience working with .net core, async/await tasks and linq, so I am not sure how to deal with this error.
Thanks
The error is caused by this:
_context.TravelPackages.Include(t => t.Products.Select
The Include statement should represent a property access but you are selecting to an anonymous object within the Include.
In this case, the navigation property is Products so change it to:
_context.TravelPackages.Include(t => t.Products).Select
This has a closing bracket after the Products navigation property. So you are then including Product and the select query follows this.
As JMP noticed there is a syntax error. But also you are trying to return an anonymous object. So try this:
return await _context.TravelPackages
.Where(package => package.Id == id)
.Select(t =>
new TravelPackage
{
Id = t.Id,
PackageName = t.PackageName,
Products = t.Products.Select(product => new Product()
{
Id = product.Id,
ProductType = product.ProductType,
ProductDescription = product.ProductDescription,
ProductCategory = product.ProductCategory,
TravelPackageId = product.TravelPackageId
})
}).FirstOrDefaultAsync();
but since you are including all properties, you can make it shorter:
return await _context.TravelPackages
.Include(t => t.Products)
.Where(package => package.Id == id)
.FirstOrDefaultAsync();
I have a Category class. It is also a self-join. I have used repository pattern. Using the repository works fine for get, insert or of update data. But when I want to get data from a relational entity, it does not work. It throws this error:
Object reference not set to an instance of an object
But, when I get data using DbContext it works fine.
public class Category
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int? CategoryId { get; set; }
//Navigation
public Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
Configuration for self join
builder.HasMany(x => x.Children)
.WithOne(x => x.Parent)
.HasForeignKey(g => g.CategoryId);
Repository class for getting all data
public IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().AsEnumerable();
}
[HttpGet]
public JsonResult LoadCategory()
{
var categories = unitOfWork.Category
.GetAll()
.ToList()
.Select(x => new
{
Id = x.Id,
Name = x.Name,
CategoryName = x.Parent.Name
}).ToList();
return Json(categories);
}
In that last method, I get the error, but when I use
ProductDbContext db = new ProductDbContext();
then it works.
LoadCategory not working right?
Suppose that you create new instance of db as general, on top;
and use like this if it is work? Try this;
[HttpGet]
public JsonResult LoadCategory()
{
var categories = unitOfWork.Category
.GetAll()
.Select(x => new
{
Id = x.Id,
Name = x.Name,
CategoryName = x.Parent.Name
}).ToList();
return Json(categories);
}
I find the problem. CategoryId is null-able. So if any item CategoryId is null then it throw error. I just check if null then neme will be blank.
var categories = unitOfWork.Category
.GetAll()
.Select(x => new
{
Id = x.Id,
Name = x.Name,
CategoryName = x.CategoryId == null ? "" : x.Parent.Name
}).ToList();
I am looking for an efficient way to calculate total number of all sub categories (all levels) for a given category, without retrieving all the data from the database
Here is my Model:
public class Category
{
public Category()
{
SubCategories = new HashSet<Category>();
}
public Guid CategoryId { get; set; }
public string Title { get; set; }
[ForeignKey("ParentCategory")]
public Guid? ParentCategoryId { get; set; }
//navigation properties
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
}
And my ViewModel:
public class CategoryViewModel
{
public CategoryViewModel()
{
SubCategories = new List<CategoryViewModel>();
}
public Guid CategoryId { get; set; }
[Required]
public string Title { get; set; }
public Guid? ParentCategoryId { get; set; }
public virtual CategoryViewModel ParentCategory { get; set; }
public virtual List<CategoryViewModel> SubCategories { get; set; }
public int TotalOfDirectSubCategories { get; set; }
public int TotalOfAllSubCategories { get; set; }
}
My AutoMapping:
cfg.CreateMap<Category, CategoryViewModel>()
.PreserveReferences()
.ReverseMap();
And finally my EF to retrieve data:
data = _ctx.Categories.Where(x => x.ParentCategoryId == categoryId)
//.Include(x => x.SubCategories)
.ToList();
Let's assume a category can contain thousands of sub categories. Since this will be SOA solution, and data will be passed from Web API into my client app, I don't want to pass all subCategories data with every web API call, so I will only need data for requested category, and corresponding counts.
Let assume we have
'cat 1' > 'cat 1 1' > 'cat 1 1 1'
'cat 1' contains exactly 2 sub categories. 'cat 1 1' contains one sub category. I can retrieve 'cat 1' data by calling web API method 'getCategory(null)', and to retrieve 'cat 1 1' data I would call 'getCategory(GuidOfCat11)'
For 'cat 1':
TotalOfDirectSubCategories - 1
TotalOfAllSubCategories - 2
And again, no subCategories will be included
Here is the best I could implement, do you think there is a better approach:
AutoMapper:
cfg.CreateMap<Category, CategoryViewModel>()
.PreserveReferences()
.ForMember(x => x.TotalOfDirectSubCategories, x => x.MapFrom(z => z.SubCategories.Count))
.ReverseMap();
Populating CategoryViewModel:
public List<CategoryViewModel> GetAllCategoriesAndTasksForParentCategoryAndUser(Guid? categoryId)
{
var data = new List<Category>();
if (categoryId == null) // retrieve root level categories
{
data = _ctx.Categories.Where(x => x.ParentCategoryId == null)
.Include(x => x.SubCategories)
.Include(x => x.Tasks)
.ToList();
}
else
{
data = _ctx.Categories.Where(x => x.CategoryId == categoryId)
.Include(x => x.SubCategories)
.Include(x => x.Tasks)
.ToList();
}
var dataToReturn = Mapper.Map<List<Category>, List<CategoryViewModel>>(data);
foreach (var category in data)
{
var vm = dataToReturn.First(x => x.CategoryId == category.CategoryId);
//subCategories
int totalCount = 0;
SubCategoriesCount(ref totalCount, category);
vm.TotalOfAllSubCategories = totalCount;
}
return dataToReturn;
}
And finally, the method:
private void SubCategoriesCount(ref int subCatCount, Category category)
{
if (category.SubCategories != null || category.SubCategories.Count() != 0)
{
subCatCount += category.SubCategories.Count();
foreach (var subCat in category.SubCategories)
{
SubCategoriesCount(ref subCatCount, subCat);
}
}
}
I have the following object(s) and I want to have a result that provides a collection of objects grouped by first category description and then grouped by subcategory description with:
Category(Count)
SubCategory(Count)
SubCategory(Count)
public class Posting
{
public Category Category { get; set; }
public SubCategory SubCategory { get; set; }
}
public class Category
{
public string Description { get; set; }
}
public class SubCategory
{
public string Description { get; set; }
}
EDIT
So I have this working like so:
foreach (var category in col.GroupBy(x => x.Category.Description).Select(x => new { CategoryName = x.Key, Items = x }).OrderBy(x => x.CategoryName))
{
Console.WriteLine(category.CategoryName);
foreach (var subCategory in category.Items.GroupBy(x => x.SubCategory.Description).Select(x => new { SubCategoryName = x.Key }).OrderBy(x => x.SubCategoryName))
{
Console.WriteLine("\t" + subCategory.SubCategoryName);
}
}
But I would like to get this into one object graph, is that possible?
This seems to be pretty close, with the caveat that the Key field of the "inner" sub-category groupings is an anonymous type containing both the Category and SubCategory. But, depending on what your application is doing, that might be a feature since you'll always have the Category available.
var results = col
.GroupBy(posting => new
{
Category = posting.Category.Description,
SubCategory = posting.SubCategory.Description
})
.GroupBy(group => group.Key.Category.Description);