I have following 2 classes. I am using EF code first with existing database.
public class Asset
{
[Key]
public decimal AssetId { get; set; }
[StringLength(20)]
public string AssetNumber { get; set; }
public decimal? DistrictId { get; set; }
public virtual District District { get; set; }
}
public class District
{
[Key]
public decimal? DistrictId { get; set; }
[StringLength(20)]
public string DistrictCode { get; set; }
[StringLength(255)]
public string Description { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
}
I need to get the list of assets where DistrictId is not null.
I am using following c# code:
IQueryable<CustomClass> assets = dbContext.Assets
.Where(a => a.DistrictId != null)
.Select(a => new CustomClass
{
});
I believe using navigation properties, you don't need to explicitly say- Where(a => a.DistrictId != null). I am unsure about it. So can you suggest what is the better way of getting list of assets where DIstrictId is not null.
Thanks,
Since you are querying directly against Assets, you're not actually using the Asset <-> District relationship.
If you were trying to get a list of Assets with Districts included, or a list of Districts and their Assets, you would be using the Navigation properties.
For the result you're trying to get, as HaukurHaf said in his comment what you're doing is the most straightforward method.
That's as straight forward as can be, note that the DistrictID is only defining the relationship, it is not the navigation property, District is.
To verify you could do
// Eager loading all District properties and then filtering
List<CustomClass> assets = dbContext.Assets
.Include(a => a.District)
.ToList()
.Where(a => a.District != null)
.Select(a => new CustomClass {})
.ToList();
// Your initial query.
List<CustomClass> assets2 = dbContext.Assets
.Where(a => a.DistrictID != null)
.Select(a => new CustomClass {})
.ToList();
You will see they both eventually have the same list.
Related
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.
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
EF7 doesn't support lazy loading of child objects, but does support the .Include() function. That being said, I'm struggling with something and am not sure if it just isn't possible in EF7, or I've just been staring at this too long.
Suppose something like the following (checking reg.Activities.Task.Ordinal (an int), Task is always null, even when I check the DB myself and am certain there is in fact a related record)...
public void SomeOtherMethod()
var r = getRegistration(User.UserName);
var act = r.Activities
.Where(a => a.IsDone == false) // unfinished
.OrderByDescending(o => o.Task.Ordinal) // Task indicates activity type, is always null
.FirstOrDefault(); // to get a user's most recent unfinished activity
//DO SOMETHING WITH ACT
}
public Registration getRegistration(string userName) {
var reg = _context.Registrations
.Where(r => r.User.UserName == userName) // this works however?
.Include(r => r.Acvitities) // List<Activity>
.FirstOrDefault();
return reg;
}
...I have the navigation properties in place in the model classes, but .Task above is null and not loaded.
Further, since the query has been projected, I can't .Include additional properties any more in the creation of act. I cant .ThenInclude in the creation of reg because class Registration does not include a definition for a Task property (but Registration does have a collection of Activities that are List<Activity>, and Activity does have a Task that is related to another table/class that defines the tasks and the order they should be presented to users per Activity.
I've tried various incantations of .Join, .Include and .ThenInclude hoping to be able to join the Task to each of the Activities while returning the Registration object, but this fails because Registration itself does not contain a Task property.
I considered creating a new issue on GitHub, but am not yet certain that it isn't very doable and I'm just not looking at this correctly.
UPDATE1: Mihail suggested using...
.Include(r => r.Activities.Select(resp => resp.Responses))
...but this yields an exception. This SO (https://stackoverflow.com/a/30151601/3246805) indicates thats for EF5 and that .ThenInclude should be used.
However, trying that suggestion...
.ThenInclude(r => r.Select(t => t.Task))
...yields the following Exception...
The properties expression 'r => {from Activity t in r select [t].Task}' is not valid. The expression should represent a property access: 't => t.MyProperty'. When specifying multiple properties use an anonymous type: 't => new { t.MyProperty1, t.MyProperty2 }'.
Parameter name: propertyAccessExpression
UPDATE2: Stafford asked for schema. Best effort at a sharable repo...
public class RegistrationData {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public MyUser User { get; set; } // MyUser : IdentityUser
//blah blah, more fields
public List<UserTask> Activitys { get; set; }
}
public class UserTask {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public bool IsDone { get; set; } = false;
[Required]
public int RegistrationId { get; set; }
[Required]
public RegistrationData Registration { get; set; }
[Required]
public int TaskId { get; set; }
[Required]
public Task Task { get; set; }
public List<UserResponse> Responses { get; set; }
}
public class Task {
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)] // ID comes from loaded config
public int Id { get; set; }
[StringLength(20, MinimumLength = 1)]
public string Name { get; set; }
[Required]
public int Ordinal { get; set; }
[Required]
public int GroupId { get; set; }
}
public class UserResponse {
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public int UserTaskId { get; set; }
[Required]
public int QuestionNumber { get; set; }
}
Use Include followed by ThenInclude for the child's child properties. The child properties may not show up in intellisense for ThenInclude, but just enter it anyway - it will compile and operate as expected.
var reg = _context.Registrations
.Where(r => r.User.UserName == userName)
.Include(r => r.Acvitities).ThenInclude(a => a.Task)
.Include(r => r.Activities).ThenInclude(a => a.SomethingElse)
.FirstOrDefault();
return reg;
I have data in two tables - an Albums table and a Tracks table. There is a one-to-many relationship between the two and the database structure mirrors my classes below. I'm trying to get an album and it's associated tracks based on the artist name (and I know that the data isn't normalized correctly - there should be an Artist entity, but my source data doesn't support that!)
public class Album
{
public string ArtistName { get; set;}
public string AlbumId { get; set; }
public string AlbumName { get; set; }
public int TrackCount { get; set; }
public string Year { get; set; }
public string Genre { get; set; }
public string CoverArt { get; set; }
public string Biography { get; set; }
// Navigation properties
public ICollection<Track> Tracks { get; set; }
}
public class Track
{
public string TrackId { get; set; }
public int TrackNumber { get; set; }
public string TrackName { get; set; }
public string AlbumId { get; set; }
}
My query to get a list of matching albums works fine:
var albums = DataContext.Albums.Where(a => a.ArtistName == artistName && a.AlbumName == album).ToList();
but the following statement doesn't work:
var tracks = DataContext.Tracks.Where(a => a.AlbumId == albums.Select(al => al.AlbumId).SingleOrDefault());
After it runs the tracks variable contains:
{System.Data.Entity.Infrastructure.DbQuery<MusicApp.Model.Track>} System.Linq.IQueryable<MusicApp.Model.Track> {System.Data.Entity.Infrastructure.DbQuery<MusicApp.Model.Track>}
I've tried appending .ToList() to my Linq statement and that just returns an error:
Additional information: Unable to create a constant value of type 'MusicApp.Model.Album'. Only primitive types or enumeration types are supported in this context.
I'm sure that the solution is really simple, but I'm new to Linq and just can't figure this one out. Any help would be much appreciated!
You can include the navigation properties you want:
var albumsWithTheirTracks = DataContext.Albums
.Where(a => a.ArtistName == artistName)
.Include("Tracks")
.ToList();
Doing this will populate each album's tracks collection with the related entities.
Your other option is to mark your Tracks navigation property as virtual in your Album model:
public virtual ICollection<Track> ...
Marking a nav property as virtual enables lazy loading and will bring along the related entities with your original query for album. If you don't choose to lazy load, you can use the Include method to specify which navigation properties you want to get along with the original album you're asking for.
var albums = DataContext.Albums.Where(a => a.ArtistName == artistName && a.AlbumName == album).ToList();
List<string> albumIds = albums.Select(s => s.AlbumId).Distinct().ToList();
var tracks = DataContext.Tracks.Where(w => albumIds.Contains(w.AlbumId)).ToList();
If you already have the album's data, why don't you try:
var tracks = from t in DataContext.Tracks
joins a in albums on a.AlbumId equals t.AlbumId
select t;
try this:
var query = from t in DataContext.Tracks
join a in DataContext.Albums on t.AlbumId equals a.AlbumId
where a.ArtistName == "artistName" && a.AlbumName == "album"
select t;
Unnecessary joins that everyone else are making, are a personal gripe of mine, as they make code hard to follow.
var query = from t in DataContext.Tracks
where t.Artists.Any(a => a.ArtistName == "artistName"
&& a.AlbumName == "album")
select t;
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