Automapper map child of same type - asp.net-mvc

I have code first that use child parent with same type
public class Comment
{
public int Id { get; set; }
public string Text { get; set; }
public int? ParentId { get; set; }
public virtual Comment Parent { get; set; }
public virtual IList<Comment> Child { get; set; }
}
Fluent API:
modelBuilder.Entity<Comment>().Property(c => c.ParentId).IsOptional();
modelBuilder.Entity<Comment>().HasMany(c => c.Child).WithOptional(c => c.Parent).HasForeignKey(c => c.ParentId);
That's fine in entity framework. But when I try to use it on Automapper i throwing an StackOverflowException.
AutoMapperConfig:
cfg.CreateMap<Comment, CommentDTO>().ForMember(d => d.Child, opt => opt.UseDestinationValue());
CommentDTO:
public class CommentDTO
{
public int Id { get; set; }
public string Text { get; set; }
public int? ParentId { get; set; }
public virtual CommentDTO Parent { get; set; }
public virtual IList<CommentDTO> Child { get; set; }
}
Controller:
Context.Comments.GetAll().AsNoTracking().ProjectTo<CommentDTO>().AsQueryable();

Since your property names in both Comment and CommentDTO are the same, you just need to instruct AutoMapper to map them and it will do it for you:
Mapper.Initialize(x =>
x.CreateMap<Comment, CommentDTO>().ReverseMap()
.PreserveReferences());
I have used ReverseMap to allow mapping in both directions. Then you can use it whenever you want like this;
var commentDto = new CommentDTO { Child = new List<CommentDTO>(), Id = 1 };
var mapped = Mapper.Map<Comment>(commentDto);
var reverse = Mapper.Map<CommentDTO>(mapped);
And one last note, in .NET naming convention, if an abbreviation contains 2 characters such as Input Output > IO then it is recommended to use upper cases for both such as System.IO. But if it is more than 2 such as Data Transfer Object > DTO then the suggestion is to use Pascal notation. So your class name should be CommentDto not CommentDTO.

Related

Automapper Custom Resolve logic using Projection

I have an EF object Account that has 4 xrefs off for different portfolio types. For simplicity, I will call these types A, B, C, and D.
public class Account
{
public long AccountId { get; set; }
public PortfolioTypeAXref { get; set; }
public PortfolioTypeBXref { get; set; }
public PortfolioTypeCXref { get; set; }
public PortfolioTypeDXref { get; set; }
}
My destination object is a flattened object.
public class AccountDto
{
public long AccountId { get; set; }
public PortfolioType PortfolioType { get; set; } //this is an enum with values A, B, C, D, Unknown
public long? PortfolioId { get; set; }
}
Each Xref object looks something like this
public class PortfolioTypeAXref
{
public long XrefId { get; set; }
public long AccountId { get; set; }
public long? PortfolioTypeAId { get; set; }
public long? HypotheticalPortfolioTypeAId { get; set; }
}
I am using linq to Ef projection to translate my object. I've also written a custom Value resolver, but when using MapFrom I noticed it says it is not for projections.
The logic for the resolver essentially checks which (if any) of the xrefs have a portfolio id assigned, and if they do, return an enum with the appropriate value.
profile.CreateMap<Account, AccountDto>()
.ForMember(dest => dest.PortfolioType, opt => opt.MapFrom<PortfolioTypeResolver>())
.ForAllOtherMembers(opt => opt.Ignore());
However, this causes an error, likely because using MapFrom with a custom resolver isn't meant to work with projections. I'm not married to the idea of using a custom resolver - it could just be a function call, but how do I set the destination property based on custom logic that uses the value of the source object?

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Attribute of LINQ result is NULL but relationship is working

I have set up 3 models, code first and the relationships seem to be working but one is causing me a problem.
I have Article, Language and Edition Classes
public class Article
{
public int ID { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
}
public class Language
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class Edition
{
public int ID { get; set; }
public Article Article { get; set; }
public Language Language { get; set; }
public string Title { get; set; }
public string Details { get; set; }
}
In my bootstrap/DBinitialiser, I can create Objects and populate them fine. The DB is created and the foreign keys for Language and Article are both present on the Edition table and correctly entered.
var engLang = new Language() {Code="en", Name="English Language"};
var altLang = new Language() {Code="xx", Name="Alternative Language"};
db.Languages.Add(engLang);
db.Languages.Add(altLang);
db.SaveChanges();
var testArt = new Article() { Name = "test" };
db.Articles.Add(testArt);
db.SaveChanges();
db.Editions.Add(new Edition(){Article = testArt, Language = engLang, Title="English Content"});
db.Editions.Add(new Edition(){Article = testArt, Language = altLang, Title="Alternative Content"});
db.SaveChanges();
I can now query the Editions and return a list of them, but the Language attribute is always NULL. The Article Attribute works fine.
var query = db.Editions.Where(r => r.Article.ID == Article.ID);
foreach (Edition item in query)
{
// item.Language => NULL
// item.Article => {Object Article}
}
I'm new to .net and Entity-Framework and can't work out why I always get this error.
I can even query by r => r.Language.ID == 1 and still get a NULL attribute on the Edition object.
Make sure you are using EF codefirst in right manner. Here you have some ambiguities. You must determine what relationships actually should exist, in your POCOs. Change classes like bellow:
public class Article
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
}
public class Language
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class Edition
{
[Key]
public int ID { get; set; }
public virtual Article Article { get; set; }
public virtual Language Language { get; set; }
public string Title { get; set; }
public string Details { get; set; }
}
With thanks to AmirHossein Mehrvarzi for helping me write my models more clearly, I believe this error to be caused by the lazy loading of entities while iterating through the result of the query. ref: Entity Framework: There is already an open DataReader associated with this Command.
Without enabling MultipleActiveResultSets I simply added an Include statement to my linq
var query = db.Editions.Where(r => r.Article.ID == Article.ID).Include(r => r.Language);
foreach (Edition item in query)
{
// item.Language => {Object Language}
// item.Article => {Object Article}
}

Enitiy Framework Query, Filtering child collections of child collections

I'm using eager loading to populate my object. Is it possible to filter child collections of child collections. The following code gives me the child collection of filtered periods. But I would also like to filter the child of periods where TradeStatus == TradeStatus.Open?
public class Route
{
public Guid RouteId { get; set; }
public string Name { get; set; }
public virtual ICollection<Period> Periods { get; set; }
}
public class Period
{
public Guid PeriodId { get; set; }
public virtual ICollection<Trade> Trades { get; set; }
public bool IsActive { get; set; }
}
public class Trade
{
public Guid PeriodId { get; set; }
public string TradeName { get; set; }
public decimal TradePrice { get; set; }
public TradeStatus TradeStatus { get; set; }
}
string routeName = "UK_USA"
Route route = Context.Set<Route>().SingleOrDefault(r => r.Name.Equals(routeName));
if (route != null)
{
Context.Entry(route).Collection(r => r.Tenors).Query()
.Where(t => t.IsActive)
.Include(t => t.Trades).Load();
}
I've tried the following but it returns
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Context.Entry(route).Collection(r => r.Tenors).Query()
.Where(t => ValidTenorName.Contains(t.Name))
.Include(t =>
t.Trades.Where(tr=> tr.TradeStatus == TradeStatus.Open).Load();
Is there a solution to this problem, or an alternative way using joins perhaps?
Thanks

Entity Framework Data Annotations equivalent of .WillCascadeOnDelete(false);

I'm currently using EF Code First 4.3 with migrations enabled, but automatic migrations disabled.
My question is simple, is there a data annotations equivalent of the model configuration .WillCascadeOnDelete(false)
I would like to decorate my class so that the foreign key relationships do NOT trigger a cascading delete.
Code sample:
public class Container
{
public int ContainerID { get; set; }
public string Name { get; set; }
public virtual ICollection<Output> Outputs { get; set; }
}
public class Output
{
public int ContainerID { get; set; }
public virtual Container Container { get; set; }
public int OutputTypeID { get; set; }
public virtual OutputType OutputType { get; set; }
public int Quantity { get; set; }
}
public class OutputType
{
public int OutputTypeID { get; set; }
public string Name { get; set; }
}
I Would like to do something like this:
public class Output
{
[CascadeOnDelete(false)]
public int ContainerID { get; set; }
public virtual Container Container { get; set; }
[CascadeOnDelete(false)]
public int OutputTypeID { get; set; }
public virtual OutputType OutputType { get; set; }
public int Quantity { get; set; }
}
This way i would be able to scaffold the migration correctly. which scaffolds the foreign key relationships to be cascade deleted at the moment.
Any ideas, other than using Model Configuration?
No there is no such equivalent. You must use fluent API to remove cascade delete selectively or you must remove OneToManyCascadeDelete convention to remove it globally.
Create a mapping class (the fluent syntax) and use the code below:
// add relationships "Post" and "User" to a "Comment" entity
this.HasRequired(t => t.Post)
.WithMany(t => t.Comments)
.HasForeignKey(d => d.PostID)
.WillCascadeOnDelete(false); // <---
this.HasOptional(t => t.User)
.WithMany(t => t.Comments)
.HasForeignKey(d => d.UserID)
.WillCascadeOnDelete(false); // <---
Here's a nice post on how to set up fluent mappings if you need more info.
Just make the FK property nullable can prevent cascade delete from happening:
public int? OutputTypeID { get; set; }

Resources