Include collection in Entity Framework Core - c#

For example, I have those entities:
public class Book
{
[Key]
public string BookId { get; set; }
public List<BookPage> Pages { get; set; }
public string Text { get; set; }
}
public class BookPage
{
[Key]
public string BookPageId { get; set; }
public PageTitle PageTitle { get; set; }
public int Number { get; set; }
}
public class PageTitle
{
[Key]
public string PageTitleId { get; set; }
public string Title { get; set; }
}
How should I load all PageTitles, if I know only the BookId?
Here it is how I'm trying to do this:
using (var dbContext = new BookContext())
{
var bookPages = dbContext
.Book
.Include(x => x.Pages)
.ThenInclude(x => x.Select(y => y.PageTitle))
.SingleOrDefault(x => x.BookId == "some example id")
.Pages
.Select(x => x.PageTitle)
.ToList();
}
But the problem is, that it throws exception
ArgumentException: The properties expression 'x => {from Pages y
in x select [y].PageTitle}' 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
What's wrong, what exactly should I do?

Try accessing PageTitle directly in ThenInclude:
using (var dbContext = new BookContext())
{
var bookPages = dbContext
.Book
.Include(x => x.Pages)
.ThenInclude(y => y.PageTitle)
.SingleOrDefault(x => x.BookId == "some example id")
.Select(x => x.Pages)
.Select(x => x.PageTitle)
.ToList();
}

Related

Automapper ignores the mapping for derived class

I have a class hierarchy as described below:
public partial class EmployeeDTO
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual string ReportsTo { get; set; }
}
public class ManagerDTO: EmployeeDTO
{
public override string ReportsTo
{
get => null;
set => throw new InvalidOperationException("ReportsTo can not be set to other than the preset value.");
}
}
public class EmployeeModel
{
public long Id { get; set; }
public string FullName { get; set; }
public EmployeeType Type { get; set; }
public string ReportsTo { get; set; }
}
public enum EmployeeType
{
Employee = 0,
Manager = 1,
}
Mapping configuration
CreateMap<EmployeeDTO, EmployeeModel>()
.ForMember(empmodel => empmodel.FullName, empdto => empdto.MapFrom(empdto => empdto.Name))
.ForMember(empmodel => empmodel.Type, empdto => empdto.MapFrom(empdto => EmployeeType.Employee));
CreateMap<ManagerDTO, EmployeeModel>()
.ForMember(model => model.FullName, dto => dto.MapFrom(dto => dto.Name))
.ForMember(model => model.Type, dto => dto.MapFrom(dto => EmployeeType.Manager));
CreateMap<EmployeeModel, ManagerDTO>()
.ForMember(dto => dto.Name, model => model.MapFrom(model => model.FullName))
.ForMember(t => t.ReportsTo, t => t.Ignore());
CreateMap<EmployeeModel, EmployeeDTO>()
.ForMember(dto => dto.Name, model => model.MapFrom(model => model.FullName));
I am trying to patch the entity as follows:
[HttpPatch]
public IActionResult Patch([FromBody] Delta<EmployeeDTO> entityDelta)
{
var existingModel = new EmployeeModel
{
Id = 123,
FullName = "XYZ",
Type = Shared.EmployeeType.Manager,
ReportsTo = null
};
var existingEntity = _mapper.Map<EmployeeDTO>(existingModel);
entityDelta.Patch(existingEntity);
// Following mapping incorrectly sets type of updatedModel as EmployeeType.Employee
var updatedModel = _mapper.Map(existingEntity, existingModel);
return Ok(updatedModel);
}
After executing the above code expected type of the updatedModel to be Manager, but the type is Employee instead.
I have tried using the other overloads for the Map. Map(entity) works as expected when explicit type is provided for the previous mapping ie. Map(model). However, this overload creates another instance of the dbmodel with the same id, which leads to further issues.
To avoid having two runtime DbEntries with the same id, I am trying to use the Map(entity, existimgModel) overload of the mapper, which is failing to give the desired outcome.
Any ideas on how to solve this?
Sample code

Getting a Dictionary from a Query with GroupBy

Given two persisted entities
public class Header
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual List<Detail> Details { get; set; }
}
and
public class Detail
{
public virtual int Id { get; set; }
public virtual Header MyHeader { get; set; }
public virtual string Text { get; set; }
public virtual object LotsOfPropertiesIDontNeed { get; set; }
}
I want to populate a new object
public class MiniHeader
{
public string Name { get; set; }
public Dictionary<int, string> DetailTexts { get; set; }
}
with only the name from the Header, and with a dictionary relating detail IDs to the associated texts. Note that Detail also has LotsOfPropertiesIDontNeed, which I would prefer not to pull
across the wire or even request from SQL Server.
With the code
IEnumerable<MiniHeader> mini =
ctx.Details.Include(d => d.MyHeader)
.GroupBy(d => d.MyHeader)
.Select(g => new MiniHeader()
{
Name = g.Key.Name,
DetailTexts = g.ToDictionary(d => d.Id, d => d.Text)
});
I get the expected
LINQ to Entities does not recognize the method 'System.Collections.Generic.Dictionary`2[System.Int32,System.String]
since .ToDictionary cannot execute on the database side. I can make it work like this:
IEnumerable<MiniHeader> mini =
ctx.Details.Include(d => d.MyHeader)
.GroupBy(d => d.MyHeader)
.AsEnumerable()
.Select(g => new MiniHeader()
{
Name = g.Key.Name,
DetailTexts = g.ToDictionary(d => d.Id, d => d.Text)
});
but I presume that LotsOfPropertiesIDontNeed will be requested of SQL Server and pulled across the wire.
Is there a way to make this work, without pulling the unnecessary fields?
You can project your results to an anonymous type and then apply AsEnumerable and later project to your class like:
IEnumerable<MiniHeader> mini =
ctx.Details.Include(d => d.MyHeader)
.GroupBy(d => d.MyHeader)
.Select(g => new
{
Name = g.Key.Name,
Details = g.Select(i => new { i.Id, i.Text }),
})
.AsEnumerable()
.Select(e => new MiniHeader()
{
Name = e.Name,
DetailTexts = e.Details.ToDictionary(d => d.Id, d => d.Text)
});
This will let you get only those field that you need and later you can use ToDictionary on an in-memory collection.

Fluent NHibernate "Could not resolve property"

I have read a lot of the questions about that same error but none since to match my exact problem. I'm trying to access the property of an object, itself part of a root object, using Fluent NHibernate. Some answers say I need to use projections, others that I need to use join, and I think it should work through lazy loading.
Here are my two classes along with the Fluent mappings:
Artist class
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Album> Albums { get; set; }
public virtual string MusicBrainzId { get; set; }
public virtual string TheAudioDbId { get; set; }
public Artist() { }
}
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
LazyLoad();
Id(a => a.Id);
Map(a => a.Name).Index("Name");
HasMany(a => a.Albums)
.Cascade.All();
Map(a => a.MusicBrainzId);
Map(a => a.TheAudioDbId);
}
}
Album class
public class Album
{
public virtual int Id { get; set; }
public virtual Artist Artist { get; set; }
public virtual string Name { get; set; }
public virtual IList<Track> Tracks { get; set; }
public virtual DateTime ReleaseDate { get; set; }
public virtual string TheAudioDbId { get; set; }
public virtual string MusicBrainzId { get; set; }
public Album() { }
}
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
LazyLoad();
Id(a => a.Id);
References(a => a.Artist)
.Cascade.All();
Map(a => a.Name).Index("Name");
HasMany(a => a.Tracks)
.Cascade.All();
Map(a => a.ReleaseDate);
Map(a => a.TheAudioDbId);
Map(a => a.MusicBrainzId);
}
}
And the error happens when this code is interpreted:
var riAlbum = session.QueryOver<Album>()
.Where(x => x.Name == albumName && x.Artist.Name == artist)
.List().FirstOrDefault();
The error happens when Fluent NHibernate tries to resolve the x.Artist.Name value:
{"could not resolve property: Artist.Name of: Album"}
What would be the correct way of doing this?
You have to think of your QueryOver query as (nearly) directly translating into SQL. With this in mind, imagine this SQL query:
select
Album.*
from
Album
where
Album.Name = 'SomeAlbumName' and
Album.Artist.Name = 'SomeArtistName'
This won't work because you can't access a related table's properties like that in a SQL statement. You need to create a join from Album to Artist and then use a Where clause:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.List()
.FirstOrDefault();
Also, since you're using FirstOrDefault, you may want to consider moving that logic to the database end. Currently, you're pulling back every record matching your criteria and then taking the first one. You could use .Take to limit the query to 1 result:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.Take(1)
.SingleOrDefault<Album>();
Another explanation is that you are missing your mapping of this property or field in a NHibernateClassMapping definition. I came here about why I was getting this error based on the following scenario.
var query = scheduleRepository.CurrentSession().Query<Schedule>()
.Where(x => x.ScheduleInfo.StartDate.Date < dateOfRun.Date);
This was giving me a Could Not Resolve Property error for StartDate. This was a head scratcher, since I use this syntax all the time.
My mapping file was the following:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
}
}
which was missing the StartDate. Changed to:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
Map(x => x.StartDate);
}
}
Which resolved the error.

Automapper Project.To breaks db IQueryable

I have entities:
[Table("Representatives")]
public class Representative
{
[Column("RepID"), Key]
public int Id { get; set; }
[Column("MemID")]
public int MemberId { get; set; }
public virtual Member Member { get; set; }
}
[Table("Members")]
public class Member
{
[Column("MemID"), Key]
public int Id { get; set; }
[ForeignKey("MemberId")]
public virtual ICollection<Representative> Representatives { get; set; }
}
where Representatives relate to Member as many to one. I want to retrieve some representatives with their parent members and project it all to some view model. I've configured automapper like this:
Mapper.CreateMap<Representative, RepresentativeViewModel>()
.ForMember(m => m.RepId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.MemberModel, a => a.MapFrom(x => x.Member));
Mapper.CreateMap<Member, MemberViewModel>()
.ForMember(m => m.MemId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.OrganizationName, a => a.MapFrom(x => x.Name));
If I run this code:
var prj = ctx.Representatives.Where(r => r.Id == 1).Project().To<RepresentativeViewModel>();
then I get expression tree that contains func invocation (debugged):
...
.Lambda #Lambda2<System.Func`2[ConsoleApplication1.Representative,ConsoleApplication1.RepresentativeViewModel]>(ConsoleApplication1.Representative $var1)
{
.New ConsoleApplication1.RepresentativeViewModel(){
Name = $var1.Name,
RepId = $var1.Id,
MemberModel = .Invoke (.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>)($var1.Member)
}
}
.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>(ConsoleApplication1.Member $var2)
{
.New ConsoleApplication1.MemberViewModel(){
MemId = $var2.Id,
OrganizationName = $var2.Name
}
}
...
so for some reason builder invokes Func instead of calling Expression. Do you have ideas why it's so? How can I make my mapper build correct expression and SQL query?

Order by Collection entity property in ASP.NET MVC 3 (entity framework)

I have two entities like:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
public virtual ICollection<EmployeeEducation> EducationList { get; set; }
}
and
public class EmployeeEducation
{
public int Id { get; set; }
public int EmployeeId { get; set; }
public int Type { get; set; }
[ForeignKey("EmployeeId")]
public virtual Employee Employee { get; set; }
}
My question is, how can I get a specific employee and this employee's education list ordered by Type property?
I have tried:
Employee employee = _work.EmployeeRepository.GetSet()
.SelectMany(e => e.EducationList, (e,d) => new { e, d })
.OrderBy(x => x.d.Type)
.Select(x => x.e)
.FirstOrDefault(e => e.Id == id);
But it does't seem to be sorting. What is the correct way to do this?
Thanks for everyone...
You do SelectMany(), but never use the produced EducationList part, becuase you do .Select(x => x.e). But couldn't life be simpler? After all, you only get 1 employee, why not sort its EducationList as soon as you need it, after having Included it, if necessary:
Employee employee = _work.EmployeeRepository.GetSet().Include("EducationList")
.FirstOrDefault(e => e.Id == id);
Depending if u are using POCO or not u should either use CreateSourceQuery() or Query()
In the case of POCO something like:
Employee employee = _work.EmployeeRepository.GetSet()
.SelectMany(e => e.EducationList, (e,d) => new { e, d })
.Query()
.OrderBy(x => x.d.Type)
.Select(x => x.e)
.FirstOrDefault(e => e.Id == id);

Categories

Resources