Creating LINQ statement against an EF Context with no relationships - c#

I cannot wrap my head around how to write a linq query against my EF context to get what I want.
1) What I have
Database with no foreign keys assigned, and a reverse engineered code first entity framework project. I tried manually adding virtual classes so EF might create implied foreign keys in the DBcontext, but I get errors on my .Include statements still.
Without the include the only thing I can think of is to use left joins, but I haven't gotten it down yet. In the end there will be 21 tables I have to get data from, but the following table outline encapsulates the majority of issues i'm facing.
Sample data structure:
Table Human: HumanId, LastFoodEatenId, FavoriteFoodId, CurrentlyDesiredFoodId
Table Food: FoodId, FoodName, FoodStuff
Table Toys: HumanOwnerId, ToyId, ToyName
Table Pets: HumanOwnerId, PetId, PetName, PetType
Table PetSurgery: PetId, SurgeryId, SurgeryPerformed
2) What I want
Given a HumanID, I want a compsite class or something like it from a single query.
Public Class QueryResult
{
public Human human {get;set;}
public Food LastEatenFood {get;set;}
public Food FavoriteFood {get;set;}
public Food CurrentlyDesiredFood {get;set;}
public IEnumerable<Toy> Toys {get;set;}
public IEnumerable<Pet> Pets {get;set;} //Includes surgeries if any
}
Is it even possible to write a single query to get this kind of information in a single db hit? I'd be fine is someone simply confirmed it is't possible. Then I can at least request we add relationships to our database.
Thanks in advance,

You can use linq to query multiple, non-related tables.
I'm going to assume a LOT about your entities, but here we go...
int humanId = 1234;
using (var context = new MyContext())
{
var human = (from h in context.Humans
join lf in context.Foods on h.LastFoodEatenId equals lf.foodId into lfg
from lf in lfg.DefaultIfEmpty() // left join
join ff in context.Foods on h.FavoriteFoodId equals lf.foodId into ffg
from ff in ffg.DefaultIfEmpty() // left join
join cf in context.Foods on h.CurrentlyDesiredFoodId equals lf.foodId into cfg
from cf in cfg.DefaultIfEmpty() // left join
join p in context.Pets on h.humanId equals p.humanId into pg // group
join t in context.Toys on h.humanId equals t.humanId into tg // group
where h.humanId = humanId
select new QueryResult { human = h, LastEatenFood = lf, FavoriteFood = ff, CurrentlyDesiredFood = cf, Toys = tg, Pets = pg }
).SingleOrDefault();
}
Note: I'm doing this from memory without a syntax checker, so ymmv. Adding surgeries should be possible as well, but may require a subquery.

I tried manually adding virtual classes
I assume you mean virtual collections. You can define relationships in a "code-first" model if they are not in the database. The only condition is that foreign keys must refer to properties that EF knows as primary keys. So you should be able to do LINQ queries using navigation properties in stead of these verbose joins by a model like this (reduced to the essentials):
class Human
{
public int HumanId { get; set; }
public int LastFoodEatenId { get; set; }
public virtual Food LastEatenFood { get; set; }
public int FavoriteFoodId { get; set; }
public virtual Food FavoriteFood { get; set; }
public int CurrentlyDesiredFoodId { get; set; }
public virtual Food CurrentlyDesiredFood { get; set; }
public virtual ICollection<Toy> Toys { get; set; }
public virtual ICollection<Pet> Pets { get; set; }
}
class Food
{
public int FoodId { get; set; }
}
class Pet
{
public int PetId { get; set; }
public int HumanOwnerId { get; set; }
}
class Toy
{
public int ToyId { get; set; }
public int HumanOwnerId { get; set; }
}
And a mapping:
class HumanMapping : EntityTypeConfiguration<Human>
{
public HumanMapping()
{
HasOptional(h => h.LastEatenFood).WithMany()
.HasForeignKey(h => h.LastFoodEatenId);
HasOptional(h => h.FavoriteFood).WithMany()
.HasForeignKey(h => h.FavoriteFoodId);
HasOptional(h => h.CurrentlyDesiredFood).WithMany()
.HasForeignKey(h => h.CurrentlyDesiredFoodId);
HasMany(h => h.Toys).WithOptional().HasForeignKey(t => t.HumanOwnerId);
HasMany(h => h.Pets).WithOptional().HasForeignKey(t => t.HumanOwnerId);
}
}
EF will infer the primary keys by name conventions.
Now you will be able to execute a LINQ statement like:
context.Humans.Where(h => h.HumanId == id)
.Include(h => h.LastEatenFood)
.Include(h => h.FavoriteFood)
.Include(h => h.CurrentlyDesiredFood)
.Include(h => h.Toys)
.Include(h => h.Pets)
From your description I understand that PetSurgery should be a junction class between Pet and another class (Surgery?). Anyway, I think you will manage creating the correct mappings, seeing this example.

Related

In LINQ, how can I do an .OrderBy() on data that came from many to many relationship?

I have already seen the answers to these questions In LINQ, how can I do an .OrderBy() on data that came from my .Include()?, ^ and ^, However, None is the answer to my question.
I have three entities: Letter, Person, LetterPerson as follows:
public class Letter
{
public int LetterId { get; set; }
public string Title { get; set; }
//MtoM
public ICollection<LetterPerson> LetterPersons { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
//MtoM
public ICollection<LetterPerson> LetterPersons { get; set; }
}
public class LetterPerson
{
public int LetterPersonId { get; set; }
[ForeignKey("Letter")]
public int LetterId { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
public DateTimeOffset? AssignDate { get; set; }=DateTimeOffset.Now;
public Letter Letter { get; set; }
public Person Person { get; set; }
}
The Letter entity has Many To Many relationship with the Person entity by the LetterPerson entity. Now, I'd like to get a list of the person according to a specific letter's id and order by on the LetterPerson's id.
I have something like the following query in mind:
var PersonRec = await _dbContext.Persons
.Include(u => u.LetterPersons)
.Where(u => u.LetterPersons.Any(i => i.LetterId == LetterId))
.OrderBy(u => u.LetterPersons.LetterPersonId)
//.Include(u => u.LetterPersons.OrderBy(f=>f.LetterPersonId))
//.Where(u => u.LetterPersons.OrderBy(f=>f.LetterPersonId).Any(i => i.LetterId == LetterId))
//.OrderBy(u => u.LetterPersons.FirstOrDefault().LetterPersonId)
.ProjectTo<PersonDTO>(_mapperConfiguration).ToListAsync();
The above commented codes are the attempts that I made, but still the desired result was not achieved. I need .OrderBy(u => u.LetterPersons.LetterPersonId) , However, it clearly gives a compile error.
Question:
How should I correct the OrderBy part?
Just note that I have to send the query as a Data Transfer Object (PersonDTO) that the same as Person entity except FirstName field.
I use EF6 in .Net6.
If the combination (LetterId, PersonId) is unique in the joining table (typical for many-to-many), then the one-to-many relation from Person to LetterPerson for specific LetterId value becomes one-to-one, hence you can use Select or SelectMany with filter to get the single LetterPerson entry, which then could be used for ordering.
For isntance, using LINQ query syntax (more natural for such type of queries):
var query =
(
from p in _dbContext.Persons
from lp in p.LetterPersons
where lp.LetterId == LetterId
orderby lp.LetterPersonId
select p
)
.ProjectTo<PersonDTO>(_mapperConfiguration);
var result = await query.ToListAsync();
Note that you don't need Include in order to access related data inside LINQ to Entities query. Also for projection queries Includes are ignored.

writing LINQ query to pivot the result

I have developed a LINQ query. Now my requirement is to create pivot query from it. I am new to LINQ, I do not know how to proceed further. Please see the attached the attached result image.
public JsonResult SchoolNikashaRpt()
{
try
{
var temp = (from n in db.Nikashas
join s in db.Schools on n.SchoolId equals s.SchoolId
join k in db.Programs on n.ProgramId equals k.ProgramId
orderby n.SchoolId
select new RptSchoolsNikashaViewModel
{
SCHOOL_NAME = s.SCHOOL_NAME
,PROGRAM_NAME = k.PROGRAM_NAME
,MAPPED_AMOUNT = n.MAPPED_AMOUNT
}).ToList();
return Json(temp, JsonRequestBehavior.AllowGet);
}
catch (Exception)
{
throw;
}
}
Involved model classes are as follows::
public class NikashaModels
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int NIKASHAId { get; set; }
[Required]
public decimal MAPPED_AMOUNT { get; set; }
[ForeignKey("ProgramId")]
public ProgramModels Program { get; set; }
public int ProgramId { get; set; }
[ForeignKey("SchoolId")]
public SchoolModels School { get; set; }
public int SchoolId { get; set; }
}
public class SchoolModels
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int SchoolId { get; set; }
public string SCHOOL_NAME { get; set; }
}
public class ProgramModels
{
[Key]
public int ProgramId { get; set; }
public string PROGRAM_NAME { get; set; }
}
So you have Nikashas, Schools and Programs. There is a one-to-many relation: Every Nikasha has exactly one School, and exactly one Program, namely the School and Program that the foreign key refers to. In the other direction: Every School has zero or more Nikashas, every Program has zero or more Nikashas.
You want for every Nikasha, some properties, and some information of its School and its Program. For this you can use one of the overloads of Enumerable.Join, if more than two tables are involved it is easier to do this using Enumerab.Select
You didn't mention it, but because I see the word db, it seems to me that you are fetching the data from a DbContext, so your Enumerables are IQueryable<...>. This doesn't influence the answer very much:
IQueryable<Nikasha> nikashas = db.Nikashas
// only if you don't want all Nikashas:
.Where(nikasha => ...);
IQueryable<School> schools = ...
IQueryable<Program> programs = ...
var result = nikashas.Select(nikasha => new RptSchoolsNikashaViewModel
{
// Get the name of the one and only School of this nikasha
SchoolName = schools
// Keep only the School that the foreign key refers to:
.Where(school => school.Id == nikasha.SchoolId)
// Select the name of the School
.Select(school => school.SchoolName)
// and take the first element
.FirstOrDefault(),
// Do something similar with the program name:
ProgramName = programs.Where(program => program.Id == nikasha.ProgramId)
.Select(program => program.ProgramName)
.FirstOrDefault(),
MappedAmount = nikasha.MappedAmount,
});
In words: from every Nikasha, make one new RptSchoolsNikashaViewModel. For this RptSchoolsNikashaViewModel use the MappedAmount of the Nikasha. To get the SchoolName, take all Schools that have a primary key value equal to the foreign key in the Nikasha. From the remaining schools (probably only one), take the name of the School. Finally take the first item from the remaining school names. Do something similar for ProgramNames.

Can I use Keyless Entity Types to query CHANGETABLE in Entity Framework Core?

I'm using SQL Server Change Tracking and I'm trying to adapt this article from Microsoft Docs to an Entity Framework application: Work with Change Tracking.
I want to run this SQL query using Entity Framework:
SELECT
P.*, CT.*
FROM
dbo.Product AS P
RIGHT OUTER JOIN
CHANGETABLE(CHANGES dbo.Product, #last_synchronization_version) AS CT
ON
P.ProductID = CT.ProductID
This is what I've got so far:
public class Product
{
public int ProductID { get; set; }
// omitted dozens of other properties
}
public class ProductChange
{
public int ProductID { get; set; }
public Product? Product { get; set; }
public long SYS_CHANGE_VERSION { get; set; }
public long? SYS_CHANGE_CREATION_VERSION { get; set; }
public char SYS_CHANGE_OPERATION { get; set; }
public byte[]? SYS_CHANGE_COLUMNS { get; set; }
public byte[]? SYS_CHANGE_CONTEXT { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductChange>()
.HasNoKey()
.ToView(null);
base.OnModelCreating(modelBuilder);
}
long lastSynchronizationVersion = ...; // obtained as described in "Work with Change Tracking"
const string sql = #"
SELECT
P.*, CT.*
FROM
dbo.Product AS P
RIGHT OUTER JOIN
CHANGETABLE(CHANGES dbo.Product, {0}) AS CT
ON
P.ProductID = CT.ProductID";
var changes = await dbContext.Set<ProductChange>.FromSqlRaw(sql, lastSynchronizationVersion);
It does not work, because EF does not understand how P.* maps to public Product? Product { get; set; }. When I remove the Product property and remove P.* from the query, things work as expected. However, I need all of the properties, not just the ID.
Copying all of Product's properties into ProductChange and making them all nullable works, but I really don't want to resort to doing that.
In practice I will be using Change Tracking not just for products, but for dozens of entity types, which all have many properties. Having to specify each property in two places just to make Entity Framework play nice with Change Tracking is not a good idea.
Is there a way to get Keyless Entity Types to do what I want? Or do I have to use ADO.NET's ExecuteReader and manually map the result?
It turns out you can use relationships with navigation properties on keyless entity types, just like you can with entity types.
Configure the relationship in OnModelCreating:
modelBuilder.Entity<ProductChange>()
.HasNoKey()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.ProductID) // I added this line.
.ToView(null);
Now you can use Include instead of manually joining tables:
const string sql = "SELECT * FROM CHANGETABLE(CHANGES dbo.Product, {0}) AS CT";
var changes = await dbContext
.Set<ProductChange>
.FromSqlRaw(sql, lastSynchronizationVersion)
.Include(x => x.Entity)
.DefaultIfEmpty() // https://stackoverflow.com/a/63006304/1185136
.ToArrayAsync();
// it works!
Additionally (this is optional), I created base types Change and Change<TEntity> that can be inherited from easily:
public abstract class Change
{
public long Version { get; set; }
public long? CreationVersion { get; set; }
public char Operation { get; set; }
public byte[]? Columns { get; set; }
public byte[]? Context { get; set; }
}
public abstract class Change<TEntity> : Change
where TEntity : class
{
public TEntity? Entity { get; set; }
}
public ProductChange : Change<Product>
{
public int ProductID { get; set; }
}
public OrderChange : Change<Order>
{
public int OrderID { get; set; }
}
// etc...
You'll have to configure the relationship for each derived type in OnModelCreating.
modelBuilder.Entity<ProductChange>()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.ProductID);
modelBuilder.Entity<OrderChange>()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.OrderID);
// etc...
You won't have to repeat HasNoKey() and ToView(null) for every enitity though, add this loop instead:
foreach (var changeType in modelBuilder.Model.FindLeastDerivedEntityTypes(typeof(Change)))
{
var builder = modelBuilder.Entity(changeType.ClrType).HasNoKey().ToView(null);
builder.Property(nameof(Change.Version)).HasColumnName("SYS_CHANGE_VERSION");
builder.Property(nameof(Change.CreationVersion)).HasColumnName("SYS_CHANGE_CREATION_VERSION");
builder.Property(nameof(Change.Operation)).HasColumnName("SYS_CHANGE_OPERATION");
builder.Property(nameof(Change.Columns)).HasColumnName("SYS_CHANGE_COLUMNS");
builder.Property(nameof(Change.Context)).HasColumnName("SYS_CHANGE_CONTEXT");
}
If you want to, you can move the ID property to Change<TEntity>. By doing this, you can remove the ProductChange, OrderChange etc. classes. But, you'll have to specify the column name so Entity Framework Core understands the ID property from Change<Product> maps to ProductID, and the ID property from Change<Order> maps to OrderID, etc. I opted not to do this because this approach won't work if you have composite keys.

How can I get the count of a list in an Entity Framework model without including/loading the entire collection?

I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]

LINQ to Entities query error

I am encountered an error that I am not familier with. I tried to google with no success.
I wrote the following query where I am having this error.
The entity or complex type 'MyWebProject.Models.UserDetail' cannot be constructed in a LINQ to Entities query.
The query:
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId
select new UserDetail()
{
FullName = k.FullName,
Email = k.Email,
About = k.About,
Link = k.Link,
UserSchool = new School()
{
SchoolId = k.UserSchool.SchoolId,
SchoolName = k.UserSchool.SchoolName
},
UserCourse = new Course()
{
CourseId=k.UserCourse.CourseId,
CourseName=k.UserCourse.CourseName
},
Country=k.Country
}).FirstOrDefault();
Class:
public class UserDetail
{
public int Id { get; set; }
public int UserId { get; set; }
public string FullName { get; set; }
public string Link { get; set; }
public bool? Verified { get; set; }
public string Email { get; set; }
public string About { get; set; }
public School UserSchool { get; set; }
public Course UserCourse { get; set; }
public string Country { get; set; }
}
public class School
{
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public string Country { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public School School { get; set; }
}
Any idea what went wrong??
It looks like it is due to how you are creating the complex properties School and Course in the middle of the query. It would be better to select the User (remove the select transformation), then use navigation properties to access those objects instead of building them manually. The navigation are meant for this as long as you have the proper relations built with foreign keys.
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId})
.FirstOrDefault();
// access navigation properties which will perform the joins on your behalf
// this also provides for lazy loading which would make it more effecient. (it wont load the school object until you need to access it)
userdata.School
userdata.Course
MSDN article about navigation properties: http://msdn.microsoft.com/en-us/library/vstudio/bb738520(v=vs.100).aspx
This should give you what you want. It will load your objects as part of the query (and not rely on lazy loading).
UsersContext db = new UsersContext();
var userdata = db.UserDetails.Include(x => x.UserSchool)
.Include(x => x.UserCourse)
.Include(x => x.Country)
.Where(x => x.UserId == WebSecurity.CurrentUserId)
.FirstOrDefault();
I think it's because your entity has the same name of the object you're trying to create. Try renaming the object you want to return back. If you want to return the same type as your entity try the eager loading with .Include("relationshipname") feature.
A great answer from #Yakimych is given below.
You cannot (and should not be able to) project onto a mapped entity. You can, however, project onto an annonymous type or onto a DTO:
public class ProductDTO
{
public string Name { get; set; }
// Other field you may need from the Product entity
}
And your method will return a List of DTO's.
public List<ProductDTO> GetProducts(int categoryID)
{
return (from p in db.Products
where p.CategoryID == categoryID
select new ProductDTO { Name = p.Name }).ToList();
}
Mapped entities in EF basically represent database tables. If you project onto a mapped entity, what you basically do is partially load an entity, which is not a valid state. EF won't have any clue how to e.g. handle an update of such an entity in the future (the default behaviour would be probably overwriting the non-loaded fields with nulls or whatever you'll have in your object). This would be a dangerous operation, since you would risk losing some of your data in the DB, therefore it is not allowed to partially load entities (or project onto mapped entities) in EF.
For more details please go to the following link:
The entity cannot be constructed in a LINQ to Entities query

Categories

Resources