By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);
Related
This is my first real attempt using Automapper and I'm struggling to properly map a many-to-many relationship using DTOs.
Here are the models:
public class Camp
{
[Key]
public long Id { get; set; }
[Required]
[MaxLength( 150 )]
public string Name { get; set; }
[Required]
[MaxLength( 150 )]
public string Location { get; set; }
[Required]
public DateTime StartDate { get; set; }
[NotMapped]
public int CampYear
{
get => StartDate.Year;
}
public bool Archived { get; set; }
public ICollection<Application> Applications { get; set; }
public ICollection<CampStaffPosition> CampStaffPositions { get; set; }
}
public class StaffPosition
{
[Key]
public int Id { get; set; }
public string PositionName { get; set; }
public ICollection<CampStaffPosition> CampStaffPositions { get; set; }
}
public class CampStaffPosition
{
public long CampId { get; set; }
public Camp Camp { get; set; }
public int StaffPositionId { get; set; }
public StaffPosition StaffPosition { get; set; }
public short PositionQuantity { get; set; } // Additional Info
}
And the DTOs I'm trying to map to:
public class CampDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public DateTime StartDate { get; set; }
public int CampYear { get; }
public bool Archived { get; set; }
public ICollection<ApplicationDto> Applications { get; set; }
public ICollection<StaffPositionDto> Positions { get; set; } // Through CampStaffPositions
}
public class StaffPositionDto
{
public int Id { get; set; }
public string Type { get; set; }
public string PositionName { get; set; }
public short PositionQuantity { get; set; } // From CampStaffPositions
}
After reading several of the other SO posts and trying to follow their examples, I've come up short. Here are a couple different mapping attempts:
CreateMap<Camp, CampDto>()
.ForMember( d => d.Positions, opt => opt.MapFrom( d => d.CampStaffPositions.Select( d => d.StaffPosition ).ToList() ) );
CreateMap<StaffPosition, CampDto>()
.ForMember( pr => pr.Positions, opt => opt.MapFrom( cp => cp.PositionName ) );
CreateMap<StaffPosition, StaffPositionDto>();
//CreateMap<StaffPosition, StaffPositionDto>()
// .ForMember( cr => cr.PositionQuantity, opt => opt.MapFrom( c => c.CampStaffPositions ) );
These are the most recent errors that I'm getting (with the commented line included):
Unable to create a map expression from StaffPosition.CampStaffPositions (System.Collections.Generic.ICollection`1[Server.Models.CampStaffPosition]) to StaffPositionDto.PositionQuantity (System.Int16)
Mapping types: StaffPosition -> StaffPositionDto Server.Models.StaffPosition -> Shared.Dto.Core.StaffPositionDto
Type Map configuration: StaffPosition -> StaffPositionDto Server.Models.StaffPosition -> Shared.Dto.Core.StaffPositionDto Destination Member: PositionQuantity
and with the commented line excluded:
Expression of type 'System.Collections.Generic.List`1[Server.Models.StaffPosition]' cannot be used for parameter of type 'System.Linq.IQueryable`1[Server.Models.StaffPosition]' of method 'System.Linq.IQueryable`1[Shared.Dto.Core.StaffPositionDto] Select[StaffPosition,StaffPositionDto](System.Linq.IQueryable`1[Server.Models.StaffPosition], System.Linq.Expressions.Expression`1[System.Func`2[Server.Models.StaffPosition,Shared.Dto.Core.StaffPositionDto]])'
How can I map the many-to-many to include the additional property from the join table without having to include the join table in my DTOs?
You need to flatten a complex object. You have properties in child objects, which you want to bring up one level higher, while still leveraging AutoMapper mapping capabilities. There is a method called IncludeMembers() (see the docs) that exists precisely for such case. It allows you to reuse the configuration in the existing maps for the child types, that way PositionName will be included from a child object StaffPosition acting as a second source when mapping from CampStaffPosition to StaffPositionDto:
config.CreateMap<Camp, CampDto>()
.ForMember(d => d.Positions, o => o.MapFrom(s => s.CampStaffPositions));
config.CreateMap<StaffPosition, StaffPositionDto>();
config.CreateMap<CampStaffPosition, StaffPositionDto>()
.IncludeMembers(p => p.StaffPosition);
config.CreateMap<Application, ApplicationDto>();
Usage:
var result = mapper.Map<List<CampDto>>(campsFromDatabase);
or using ProjectTo():
var result = await dbContext
.Set<Camp>()
.ProjectTo<CampDto>(mapper.ConfigurationProvider)
.ToListAsync();
this is my query
var geometrias = _context.Geometrias
.Include(g => g.GeometriaRegistos)
.Include(g => g.Matriz)
.ThenInclude(m => m.Referencia)
.Where(g => g.Matriz.ReferenciaId == referenciaId && !g.IsDeleted && !g.Matriz.IsDeleted)
.OrderByDescending(g => g.Id)
.Take(20)
.ToList();
i would like to get only 20 GeometriaRegistos per Geometria
but
.Include(g => g.GeometriaRegistos.Take(20))
does not work
here are the models
public class GeometriaRegisto
{
public int Id { get; set; }
public float X1 { get; set; }
public float X2 { get; set; }
public float X3 { get; set; }
public float X4 { get; set; }
public int GeometriaId { get; set; }
public Geometria Geometria { get; set; }
public int ProducaoRegistoId { get; set; }
public ProducaoRegisto ProducaoRegisto { get; set; }
}
public class Geometria
{
public int Id { get; set; }
public string Componente { get; set; }
public bool IsDeleted { get; set; }
public int MatrizId { get; set; }
public Matriz Matriz { get; set; }
public ICollection<GeometriaRegisto> GeometriaRegistos { get; set; }
}
I used to do this with Dapper and SQL stored procedure and i'm trying to use linq to manage this but i cannot find a way to do it without having to load all of them on memory first and then filter again but it's a lot of data
Unfortunately this scenario is not supported.
When you .Include you're actually signaling that you desire the JOIN of the relationship.
In order to achieve what you desire, i'd recommend you to fetch all yours Geometrias without including GeometriasRegistros. Then select the ids you will fetch like grIds = geometrias.Select(g=>g.GeometriasRegistros.Take(20).Id).Distinct(). Next step is to query based on these Ids and to wrap-it, just populate your collection manually...
not a work of art, but will work
In continuation of yesterday's post
Two Entities
public class Realtor
{
public Realtor()
{
Guid = Guid.NewGuid();
Registration = DateTime.Now;
}
public int Id { get; set; }
public Guid Guid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Registration { get; set; }
public int SubdivId { get; set; }
public Subdiv Subdiv { get; set; }
}
public class Subdiv
{
public Subdiv()
{
Created = DateTime.Now;
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public List<Realtor> Realtors { get; set; }
}
I spend test
I added one Subdiv (TOSTER TM) and received his ID
Next, I add a Realtor and I push Subdiv property found on the ID, the newly created TOSTER TM
Realtor.Subdiv is an object of type Subdiv. OK.
Then I try to select from the base the newly added Realtor.
Realtor.Subdiv = null OMG!!
We get Subdiv object, which is lacking in Realtor above and see his List<Realtor> = null
Please help in solving this problem.
Try this:
Relator rl = Context.Relators.Include(r => r.Subdiv).First(s => s.Id == id);
Now you can access to Subdiv property
For more related date you can call Include Methods more times:
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.Include(r => r.AnotherRel)
.First(s => s.Id == id);
For Entities with multiple levels in depth:
If Subdir is a collection
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.ThenInclude(sub => sub.SecondLevelDepth)
.First(s => s.Id == id);
if Subdir is an Entity
Relator rl = Context.Relators
.Include(r => r.Subdiv.Select(s => s.SecondLevelDepth)
.First(s => s.Id == id);
The problem is not with saving the related data (it should be saved correctly, you could check that inside the database), but loading it.
EF Core currently does not support lazy loading, so in order to get the related data you need to explicitly request it (the so called eager loading):
Realtor rl = context.Realtors.Include(r => r.Subdiv).First(r => r.Id == id);
For more info, see EF Core: Loading Related Data.
Entity Framework core allows to save related entities, you need to define Fluent API settings for both Master and detail table
public class Order
{
public int Id { get; set; }
public int AddressId { get; set; }
public string DeliveryNotes { get; set; }
public int PurchaseOrderNo { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UserPrice { get; set; }
public string Comment { get; set; }
[ForeignKey("OrderId ")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
Inside the DBConext OnModelCreating method , define the relation like, it will make sure when you have order object, its related or child objects i.e Enrolments will also be saved
modelBuilder.Entity<Order>()
.HasMany(c => c.Items)
.WithOne(e => e.Order);
modelBuilder.Entity<OrderItem>().Ignore(x => x.Order);
Now your code should look like this
Order _order = new Order{ AddressId = 1, DeliveryNotes ="some notes", PurchaseOrderNo =1};
_order.Items = new List< OrderItem>();
_ordert.Items.add(new OrderItem{ ProductName =”Laptop”, Quantity =1, UserPrice =1500.00, Comment =”some testing comments”});
repository.order.insert(_order);
repository.save();
I have two API calls. GetExam and SaveExam. GetExam serializes to JSON which means by the time I go to save, the entity is detached. This isnt a problem, I can go retrieve the entity by its primary key and update its properties manually.
However, when I do so the exam questions get its current collection duplicated. For example, if examToSave.ExamQuestions had a few questions deleted, and a new one added all selectedExam.exam_question are duplicated and the new one is added in. Eg. if 3 questions existed, I deleted 1 and added 4 there will now be 7.
Domain models:
public partial class exam
{
public exam()
{
this.exam_question = new HashSet<exam_question>();
}
public int ID { get; set; }
public string ExamName { get; set; }
public string ExamDesc { get; set; }
public Nullable<decimal> TimeToComplete { get; set; }
public bool AllowBackStep { get; set; }
public bool RandomizeAnswerOrder { get; set; }
public int Attempts { get; set; }
public virtual ICollection<exam_question> exam_question { get; set; }
}
public partial class exam_question
{
public exam_question()
{
this.exam_answer = new HashSet<exam_answer>();
}
public int ID { get; set; }
public int ExamID { get; set; }
public string QuestionText { get; set; }
public bool IsFreeForm { get; set; }
public virtual exam exam { get; set; }
public virtual ICollection<exam_answer> exam_answer { get; set; }
}
public partial class exam_answer
{
public int ID { get; set; }
public string AnswerText { get; set; }
public int QuestionID { get; set; }
public bool IsCorrect { get; set; }
public virtual exam_question exam_question { get; set; }
}
Save method:
[Route("SaveExam")]
[HttpPost]
public IHttpActionResult SaveExam(ExamViewModel examToSave)
{
using (var db = new IntranetEntities())
{
// try to locate the desired exam to update
var selectedExam = db.exams.Where(w => w.ID == examToSave.ID).SingleOrDefault();
if (selectedExam == null)
{
return NotFound();
}
// Redacted business logic
// Map the viewmodel to the domain model
Mapper.CreateMap<ExamAnswerViewModel, exam_answer>();
Mapper.CreateMap<ExamQuestionViewModel, exam_question>().ForMember(dest => dest.exam_answer, opt => opt.MapFrom(src => src.QuestionAnswers));
Mapper.CreateMap<ExamViewModel, exam>().ForMember(dest => dest.exam_question, opt => opt.MapFrom(src => src.ExamQuestions));
var viewmodel = Mapper.Map<exam>(examToSave);
// Update exam properties
selectedExam.ExamName = viewmodel.ExamName;
selectedExam.ExamDesc = viewmodel.ExamDesc;
selectedExam.AllowBackStep = viewmodel.AllowBackStep;
selectedExam.Attempts = viewmodel.Attempts;
selectedExam.RandomizeAnswerOrder = viewmodel.RandomizeAnswerOrder;
selectedExam.exam_question = viewmodel.exam_question; // DUPLICATES PROPS
// Save
db.SaveChanges();
return Ok(examToSave);
}
}
I am using Entity Framework 5 and I have these classes. What I want to do is to be able to get the data to populate the view listed below:
public partial class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
}
public partial class Topic
{
public int TopicId { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public string Name { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
Now I am trying to write a LINQ query to populate this class:
public class TopicSubTopicSelect
{
public int TopicId { get; set; }
public int SubTopicId { get; set; }
public string TopicName { get; set; }
public string SubTopicName { get; set; }
}
So far I have this:
return _subjectsRepository
.GetAll()
.Where(s => s.SubjectId == subjectId)
.Include(s => s.Topics.SelectMany(t => t.SubTopics))
.AsEnumerable()
.Select(item => new TopicSubTopicSelect(item.TopicId <<<
item.SubTopicId <<
item.Topic.Name <<
item.Name <<))
.ToList();
Can someone tell me how I can get data from the fields I marked with <<. I tried to do .item.Topic.TopicId etc but that does not seem to work.
You shouldn't start from Subject. You just start from SubTopic Repository, and you won't even need to use .Include. Do it like this:
_subTopicRepository
.GetAll()
.Where(s => s.Topic.SubjectId == subjectId)
.Select(s => new TopicSubTopicSelect()
{
TopicId = s.TopidId,
SubTopicId = s.SubTopicId,
TopicName = s.Topic.Name,
SubTopicName = s.Name
})
.ToList();
As I mentioned in my comment on ataravati's answer, you shouldn't actually have a SubTopicRepository so you are correct in starting at SubjectsRepository however you are querying by the Subject ID so you shouldn't be going via GetAll(), you should have a Get(int id) method. The include should be handled as an implementation detail inside Get as the children (SubTopics) are part of the Subject. That makes the method call look like this instead:
return _subjectsRepository
.Get(subjectId)
.SelectMany(subject => subject.SubTopics))
.Select(subTopic => new TopicSubTopicSelect
{
TopicId = subTopic.TopicId,
SubTopicId = subTopic.SubTopicId,
TopicName = subTopic.Topic.Name,
SubTopicName = subTopic.Name
}).ToList();