I have three classes:
Country.
District.
Province
public class Country
{
public Country()
{
Districts = new HashSet<District>();
}
public string Name { get; set; }
public virtual ICollection<District> Districts { get; private set; }
}
public class Ditrict
{
public Ditrict()
{
Provinces = new HashSet<Province>();
}
public string Name { get; set; }
public virtual ICollection<Province> Provinces { get; private set; }
}
public class Province
{
public string Name { get; set; }
}
What I need to accomplish is how to include all Provinces, Districts (Even if there is no Provinces), Country (Even if there is no Districts) in one command using Ling.
The reason is that I am going to use DevExpress Grid, so the user can see the Country and add a new District, and can see the District and add a new Province.
My all attempts failed, because what I get is the Country that have Disctrict and District that have Province.
Btw, I am using:
VS 2015
EF Core
MS SQL 2016
[Solution]
After many attempts and searching, I discovered that I need to use .ThenInclude to the third level Provinces. Like:
Countries = dbContext
.Countries
.Include(c => c.Districts)
.ThenInclude(p => p.Provinces)
.ToList();
It would have been nice if you could send us what you have tried. Roughly speaking you can use Include to eager load dependent entities and collections. If you query the chain of entities starting from Country it will retrieve all the countries even if they don't have Districts:
using (var dbContext = new YourDbContext())
{
return dbContext.Countries
.Include(c => c.Districts.Select(d => d.Provinces))
.ToList();
}
I guess the reason you retrieve countries only if they have districts, etc, is because your query starts from the other end of the chain (Province) rather than starting from Country as above.
Related
I am using a DATABASE-FIRST approach in C# MVC and all of my generated models are in a sub-folder Models>Generated. One of these models is called SourceSystem which contains the field definitions of the table and the related table entities.
public partial class SourceSystem
{
[Key]
[Column("ID")]
public int Id { get; set; }
[StringLength(100)]
public string SystemName { get; set; } = null!;
[Column("ROWSTAMP")]
public byte[] Rowstamp { get; set; } = null!;
[StringLength(100)]
public string? LinkedServerName { get; set; }
[StringLength(100)]
public string? DatabaseName { get; set; }
[StringLength(20)]
public string? DefaultSourceSchema { get; set; }
[StringLength(20)]
public string? DefaultTargetSchema { get; set; }
[InverseProperty("SourceSystem")]
public virtual ICollection<Domain> Domains { get; } = new List<Domain>();
[InverseProperty("SourceSystem")]
public virtual ICollection<EventProfile> EventProfiles { get; } = new List<EventProfile>();
}
As part of the application there are also a number of synonyms created which will link back to the source databases (based on the Linked Server Name, Database Name and Default Source Schema. This list of synonymns does not live in MY database but are in the msdb database so I have a view that enables me to generate a dataset of the synonyms and associate them back to the SourceSystem table. For note, DelimitedSpit8K takes a string and spits it up, into a record set. Because synonyms use a 2/3/4 part naming convention, I have to reverse them as I need to definately have the last two parts (schema and object name) but the first two (linked server name and database) are optional. Note also that the schema for the view is pow, not the default dbo.
CREATE VIEW pow.Synonymn AS
SELECT
SYN.object_id AS [SystemID]
,SYN.name AS [Synonym]
,SCH.name AS [SourceSchema]
,SYN.base_object_name
,REPLACE(REPLACE(REVERSE(object_name.Item),'[',''),']','') AS [object_name]
,REPLACE(REPLACE(REVERSE(object_schema.Item),'[',''),']','') AS [object_schema]
,REPLACE(REPLACE(REVERSE(object_db.Item),'[',''),']','') AS [object_db]
,REPLACE(REPLACE(REVERSE(object_linked_server.Item),'[',''),']','') AS [object_linked_server]
,SS.ID AS [SourceSystem_Id]
FROM
sys.synonyms AS SYN
JOIN
sys.schemas AS SCH ON SCH.schema_id = SYN.schema_id
JOIN
pow.SourceSystem AS SS ON SS.DefaultTargetSchema = SCH.name
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_name]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_schema]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_db]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_linked_server]
WHERE
object_name.ItemNumber =1
AND
object_schema.ItemNumber = 2
AND
object_db.ItemNumber = 3
AND
(
object_linked_server.ItemNumber IS NULL
OR
object_linked_server.ItemNumber = 4
)
I have manually added a model to my models folder (not Models>Generated):
using Overwatch_API.Models.Generated;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
namespace Overwatch_API.Models;
//[Table("Synonym", Schema = "pow")]
public partial class Synonym
{
[Key]
[Column("SystemID")]
public int SystemID { get; set; }
[Column("Synonym")]
public string SynonymName { get; set; }
[Column("SourceSystemTargetSchema")]
public string SourceSchema { get; set; } = null!;
[Column("SourceSystemId")]
public int SourceSystem_Id { get; set; }
public string base_object_name { get; set; }
public string object_name { get; set; }
public string object_schema { get; set; }
public string object_db { get; set; }
public string object_linked_server { get; set; }
[ForeignKey("SourceSystemId")]
[InverseProperty("Synonyms")]
//[JsonIgnore]
public virtual SourceSystem SourceSystem { get; set; } = null!;
}
and I have modified the database context:
using Overwatch_API.Models;
...
public virtual DbSet<Synonym> Synonyms { get; set; }
...
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView(nameof(Synonym))
.HasKey(t => t.SystemID);
});
and I have updated the ViewModel for the SourceSystemVM:
using Overwatch_API.Models;
...
public class SourceSystemVM
{
public int Id { get; set; }
[DisplayName("System Name")]
public string SystemName { get; set; }
[DisplayName("Linked Server")]
public string? LinkedServerName { get; set; }
[DisplayName("Linked Database")]
public string? DatabaseName { get; set; }
[DisplayName("Source Schema")]
public string? DefaultSourceSchema { get; set; }
[DisplayName("Target Schema")]
public string? DefaultTargetSchema { get; set; }
public ICollection<DomainVM> domains { get; set; }
public ICollection<Synonym> synonyms { get; set; }
public SourceSystemVM(SourceSystem ss)
{
Id = ss.Id;
SystemName = ss.SystemName;
LinkedServerName = ss.LinkedServerName;
DatabaseName = ss.DatabaseName;
DefaultSourceSchema = ss.DefaultSourceSchema;
DefaultTargetSchema = ss.DefaultTargetSchema;
domains = new List<DomainVM>();
synonyms = new List<Synonym>();
}
}
When I start the server and run Swagger, and choose the api endpoint
https://localhost:7001/api/SourceSystems
I get the following error message:
InvalidOperationException: The [InverseProperty] attribute on property 'Synonym.SourceSystem' is not valid. The property 'Synonyms' is not a valid navigation on the related type 'SourceSystem'. Ensure that the property exists and is a valid reference or collection navigation.
I am not sure what part of the configuration I have got wrong. I don't want to touch the SourceSytem.cs in the Models>Generated folder as it will get overwritten if the DF models are re-generated. Do I need to create a new partial class in the models folder to extend the generated model, and if so, what would that look like and how do I disambiguate between the Models>SourceSystem.cs and the Models>Generated>SourceSystem.cs when referencing it in the VM and DTOs. Or am I missing an entire concept somewhere?
For context, the Synonym collection is used for view (read) only. The functionality to add a new synonym will have to be managed through a call to a SQL stored procedure, but I need to understand what I have screwed up here first :)
UPDATE
I have added the partial class to the Models folder:
using Overwatch_API.Models;
using Overwatch_API.Models.Generated;
using System.ComponentModel.DataAnnotations.Schema;
namespace Overwatch_API.Models.Generated
{
public partial class SourceSystem
{
[InverseProperty("SourceSystem")]
public virtual ICollection<Synonym> Synonyms { get; } = new List<Synonym>();
}
}
and updated the context:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
});
and now the API doesn't throw an error message but the synonym array is empty and I'm not sure why: Whether the relationship between the Synonym and SourceSystems is not defined correctly or if the view is not being found/executed to return the details.
UPDATE 2: As per the question from Alex:
I have set up the following in the dbContext:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
entity.HasOne(d => d.SourceSystem)
.WithMany(p => p.Synonyms)
.HasForeignKey(x => x.SourceSystemId);
});
The Profiler is showing the following queries being run. For the Synonymns API [HttpGet]
SELECT [s].[SystemID], [s].[SourceSchema], [s].[SourceSystem_ID], [s].[Synonym], [s].[base_object_name], [s].[object_db], [s].[object_linked_server], [s].[object_name], [s].[object_schema]
FROM [pow].[Synonym] AS [s]
For the SourceSystem API [HttpGet]
SELECT [s].[ID], [s].[DatabaseName], [s].[DefaultSourceSchema], [s].[DefaultTargetSchema], [s].[LinkedServerName], [s].[ROWSTAMP], [s].[SystemName], [d].[ID], [d].[DomainName], [d].[ROWSTAMP], [d].[SourceSystem_ID]
FROM [pow].[SourceSystem] AS [s]
LEFT JOIN [pow].[Domain] AS [d] ON [s].[ID] = [d].[SourceSystem_ID]
ORDER BY [s].[ID]
Domain is another collection within SourceSystem but it unrelated to the Synonyms. A single join here would create a cartesion collection with both the Domains and Synonymns being repeated. Could this be the problem? The data fetch would either need to do an N+1 query or bring back the cartesian collection and then filter distinct. If so, how do I get around the problem. Is there a way to lazy-load the synonymns in MVC. I could just load them all in the front end (React/Next) and apply a filter in JS to only show the ones connected with the selected SourceSystem but this is spreading the logic about throughout the application stack.
OK. So I worked it out and it is non-trivial so hoping that this helps someone else.
SourceSystems contains multiple ICollections (Domains and Synonyms amongst them) however these create cyclic dependencies so the get uses a SourceSystemViewModel which uses a DomainViewModel which does not contain the cyclic reference back to the SourceSystem. I had to add the Synonyms to the SourceSystemViewModel, but as the Synonyms also contain the cyclic reference I have to create a SynonymViewModel as well.
and then in the SourceSystemsController, when executing the _context.SourceSystems you have to tell it to .Include("ChildCollection") which I had not done.
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
Once this is included you then have to specifically iterate through the ssList, and for each SourceSystemDTO, iterate through both the Domains list and the Synonyms list and map the list items into the relevant arrays.
[HttpGet]
[ResponseType(typeof(List<SourceSystemVM>))]
public async Task<IEnumerable<SourceSystemVM>> GetSourceSystems()
{
List<SourceSystem> ss = _context.SourceSystems.ToList();
IEnumerable<SourceSystemVM> ssDTO = from s in ss select new SourceSystemVM(s);
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
var ssDTOList = new List<SourceSystemVM>();
ssList.ForEach(ss =>
{
var ssDTO = new SourceSystemVM(ss);
foreach (var domain in ss.Domains)
{
var domainDTO = new DomainVM(domain);
ssDTO.domains.Add(domainDTO);
}
foreach (var synonym in ss.Synonyms)
{
var synonymDTO = new SynonymVM(synonym);
ssDTO.synonyms.Add(synonymDTO);
}
ssDTOList.Add(ssDTO);
});
return ssDTOList;
}
I have pretty much solved this problem but I am wondering whether there is a more efficient way of doing this using Entity framework / SQL.
Essentially, what i am doing is performing a subquery to get a SINGLE item on a list of objects that are connected to a parent entity. I then want to extract only a few columns from that single entity.
The first way, which doesn't work but shows my possible thought process was to put each object into a temporary variable and then create the view:
_context.IcoInfos.Select((i) =>
{
var reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault();
return new IcoInfoRewardCountViewModel()
{
CampaignName = i.Name,
CurParticipants = reward.CurParticipants,
Title = reward.CustomTitle,
IsLive = reward.IsLive
};
});
The second way, which works, I am creating a temporary model which stores the single database row of the sublist result...
_context.IcoInfos.Select((i) => new
{
Reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault(),
IcoName = i.Name
}).Select(t => new IcoInfoRewardCountViewModel()
{
CampaignName = t.IcoName,
CurParticipants = t.Reward.CurParticipants,
Title = t.Reward.CustomTitle,
IsLive = t.Reward.IsLive
}).ToList();
My question is, is this second way the only/best way to achieve this?
Your second approach is ok but for bigger application will cause you trouble if application growth larger and you have a lot information to store in the model.
So I think you can use automapper to make your code more clean.
Example
To use autoampper I need to define a model class and DTO class that share some same properties.
public class Comment
{
public string Content { get; set; }
public virtual Comment ParentComment { get; set; }
public virtual Post Post { get; set; }
public virtual User? User { get; set; }
public CommentStatus CommentStatus { get; set; }
}
public class CommentDto
{
public int Id { get; set; }
public Guid UniqeId { get; set; }
public string Content { get; set; }
public Comment ParentComment { get; set; }
public CommentStatus CommentStatus { get; set; }
public DateTime DateCreated { get; set; }
}
I also need to define the profile class to register mapping
public class CommentProfile : Profile
{
public CommentProfile()
{
CreateMap<Comment, CommentDto>(MemberList.None).ReverseMap();
}
}
Then I will need to register into DI container in startup.cs
services.AddAutoMapper();
Then I can use like this
var comments = await _unitOfWork.Repository<Comment>().Query()
.Include(x => x.User)
.Include(c => c.Post)
.Select(x => new CommentViewModel
{
Comment = _mapper.Map<Comment, CommentDto>(x),
})
.ToListAsync();
It will make the code more clear and I dont have to do manual mapping
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]
I have two models:
public class HouseType
{
public int Id { get; set; }
public string TypeName { get; set; }
public virtual IEnumerable<HouseModel> HouseModels { get; set; }
}
and
public class HouseModel
{
public int Id { get; set; }
public string ModelName { get; set; }
[DisplayFormat(DataFormatString = "{0:n2}")]
public double StandardPrice { get; set; }
[ForeignKey("HouseType")]
public int HouseTypeID { get; set; }
public virtual HouseType HouseType { get; set; }
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
}
I am returning a JSON result, so as expected I cannot manipulate it in a view, because the display is handled by a javascript file that I made.
I am trying to retrieve the number of HouseModel that is contained by HouseType. I have tried:
db.HouseTypes.Select(h => new
{
HouseCount = h.HouseModels.Count()
}).ToList();
But Entity Framework complains about it. How can I access the count of related records inside an entity? Any help will be much appreciated. Thanks.
Use
public virtual ICollection<HouseUnit> HouseUnits { get; set; }
instead of
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
Hope this helps.
Simply speaking, the trouble is that EF is trying to execute the .Select() statement on the db server but, of course, the db server does not know how to create a new object.
You first need to bring back the counts then create your objects so something like this should work better:
var listOfCounts = db.HouseTypes
.Select(h => h.HouseModels.Count())
.ToList()
.Select(c => new
{
HouseCount = c
})
.ToList();
in this example when the first .ToList() is executed the db needs only return a set of numbers (the counts of HouseModels in each HouseType) then we have a List<int> in local memory from which we can create our objects with the second Select statement.
As an aside...
It wasn't part of your original question but maybe you'd want to consider a dictionary rather than a list so you have some means of identifying which count of HouseModels belonged to each HouseType? in which case we could do something like:
Dictionary<int,string> houseModelCounts = db.HouseTypes
.ToDictionary(h => h.Id, h => h.HouseModels.Count());
which would give a dictionary keyed with the HouseType Id with values for the count of HouseModels in each type. I don't know your context though so maybe unnecessary for you?
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