Self Join in Entity Framework Core - c#

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

Related

Error when using Select() instead of Include() in a query

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

ASP.NET Core Web API: having trouble with async task?

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

Filtering "include" entities in EF Core

I have the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();

LINQ to Entities, Where Any In

How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();

RavenDb Hierarchial Data Index

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

Categories