Automapper Custom Resolve logic using Projection - entity-framework-6

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?

Related

Automapper map child of same type

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.

Viewmodel set up Aspt.net MVC 6

I'm having trouble understanding how to implement a ViewModel in Asp.net MVC, I have the following tables:
Form
ID, Data
Report
ID, FormID, Owner, Category, Status, SubmissionDate
ReportValues
ID, ReportID, Title, Value
I'm looking for a way to display and edit Report and ReportValues in the one ViewModel where ReportValues.ReportID = Report.ID
ReportValues will have multiple entries that relate to a Report.
I have had a look at similiar questions on here and tried following a tutorial ( http://techfunda.com/howto/262/list-data-using-viewmodel ) and coming up empty handed.
If you need any more information let me know and thanks in advance for any replies!
Your View Model is nothing more than a class. You can solve this many ways, but here's an example.
Create your 3 classes like you normally would.
public class Form
{
public int Id { get; set; }
public string Data { get; set; }
}
public class ReportValues
{
public int Id { get; set; }
public int ReportId { get; set; }
public string Title { get; set; }
public string Value { get; set; }
}
public class Report
{
public int Id { get; set; }
public int FormId { get; set; }
public string Owner { get; set; }
public string Category { get; set; }
public string Status { get; set; }
public DateTime SubmissionDate { get; set; }
}
Then, create your ViewModel class to include the three above classes like this.
public class ReportViewModel
{
public Form Form { get; set; }
public ReportValues ReportValues { get; set; }
public Report Report { get; set; }
}
In your view you can access your three classes and their properties as you would in your controller. Model.Form.Id
Depending on your data types, ReportValues will likely be a property of Report, but that's entirely up to your data structure. You will need to populate the classes using whatever method you want (Entity Framework, ADO, etc.) before you can pass them to your view and use them.

Entity relationship not working

public class Slider_Locale
{
[Key]
public int Slider_LocaleID { get; set; }
[ForeignKey("Culture")]
public int CultureID { get; set; }
public string Slogan { get; set; }
public virtual Culture Culture { get; set; }
}
public class Culture
{
[Key]
public int CultureID { get; set; }
public string CultureName { get; set; }
public string DisplayName { get; set; }
public virtual Slider_Locale slider_Locale { get; set; }
}
It gives error as follows:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role
'Slider_Locale_Culture_Source' in relationship
'Slider_Locale_Culture'. Because the Dependent Role properties are not
the key properties, the upper bound of the multiplicity of the
Dependent Role must be �*�.
How could I design the relationship?. Please help me as I am newbie in mvc and entity.
This is one of those things that's a little tricky to wrap your brain around at first. The issue is that you're trying to set up a 1:1 (or 1:0) mapping, but there's nothing in your model to enforce that kind of mapping. For example, what if you have more than one Slider_Locale object with the same CultureID value? How would your application know which one to pick?
Now, you might know that this will never happen, but the Entity Framework doesn't, and it has to err on the side of caution, so it won't let you set up a relationship that it can't prove is consistent with the table structure. Ideally, it would let you specify unique constraints other than a primary key to work around this, and maybe someday it will, but for now the simplest way around this is to change it to a one-to-many mapping. For example, you could do:
public class Slider_Locale
{
[Key]
public int Slider_LocaleID { get; set; }
[ForeignKey("Culture")]
public int CultureID { get; set; }
public string Slogan { get; set; }
public virtual Culture Culture { get; set; }
}
public class Culture
{
[Key]
public int CultureID { get; set; }
public string CultureName { get; set; }
public string DisplayName { get; set; }
// Note that this got changed to ICollection<>
public virtual ICollection<Slider_Locale> slider_Locales { get; set; }
}
Another thing you could do is change the classes so that they share the same primary key values, but in order to do that you'll have to make at least one of the relationships optional. I could give an example of this if you let me know whether Slider_Locale.Culture can be null, or Culture.slider_Locale, or both.

ASP.NET MVC 3 EF Code First - How do I make a model that optionally refers to a parent of its own type?

I'm trying to create a model that can optionally refer to a parent of the same type, for example:
public class Category
{
public virtual long CategoryID { get; set; }
public virtual Category? ParentCategory { get; set; }
public virtual int UserID { get; set; }
public virtual string Name { get; set; }
}
As you can see there is an optional member called ParentCategory that is optional and refers to a class of type Category (i.e. the same type). As I'm sure you can guess, I'm trying to create a simple Category tree, where the root node(s) will not have a parent.
This results in the following error when the Entity Framework tries to create the database:
"The ForeignKeyAttribute on property 'ParentCategoryID' on type 'MyProject.Models.Category' is not valid. The navigation property 'Category' was not found on the dependent type 'MyProject.Models.Category'. The Name value should be a valid navigation property name."
I also tried this:
public class Category
{
public virtual long CategoryID { get; set; }
[ForeignKey("Category")]
public virtual long? ParentCategoryID { get; set; }
public virtual int UserID { get; set; }
public virtual string Name { get; set; }
}
But again this resulted in the same error.
Is it possible to model this using EF Code First? Its easy to model it int he database if I were to create the database manually.
Thanks in advance
Ben
Your first example wouldn't even compile because T?, a shortcut for Nullable<T> can only be applied to value types.
The following works fine here:
public class Category
{
public virtual long CategoryID { get; set; }
public virtual Category ParentCategory { get; set; }
}
Now, this will use an ugly name by default for the FK, ParentCategory_CategoryID.
This is a way to get a nicer name, plus some flexibility when using it:
public class Category
{
public virtual long CategoryID { get; set; }
[ForeignKey("ParentCategoryID")]
public virtual Category ParentCategory { get; set; }
public virtual long? ParentCategoryID { get; set; }
}

MVC3 model set up - Code First EF

I'm trying to create a list of train journeys (among other things) in MVC, using code first Entity Framework and wondered how I could map foreign keys for the stations. The Journey model/table will have a DepartureStationID and an ArrivalStationID which will be foreign keys linking to one table/model, called Station.
Here is the code for both these models:
public class Station
{
public int StationID { get; set; }
public string StationName { get; set; }
public string StationLocation { get; set; }
}
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
public virtual Operator Operator { get; set; }
public virtual Station DepartureStation { get; set; }
public virtual Station ArrivalStation { get; set; }
}
There is another foreign key value in there, namely Operator and that has mapped successfully, but the departure and arrivals haven't, and return null values in the view: (#Html.DisplayFor(modelItem => item.DepartureStation.StationName).
When I looked in the database, there had been two additional fields created by EF:
DepartureStation_StationID
ArrivalStation_StationID
And the SQL relationship was between the station table and the two fields above, rather than DepartureID and ArrivalID
So, my question is - Do I need to do something different in the model when referencing the same table for two fields? I don't know why those additional fields were added so I presume I've set up the model incorrectly.
Thanks
For completeness, here's the same thing with fluent configuration.
public class MyDb : DbContext
{
public DbSet<Journey> Journeys { get; set; }
public DbSet<Operator> Operators { get; set; }
public DbSet<Station> Stations { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Journey>()
.HasRequired(j => j.DepartureStation)
.WithMany()
.HasForeignKey(j => j.DepartureID);
builder.Entity<Journey>()
.HasRequired(j => j.ArrivalStation)
.WithMany()
.HasForeignKey(j => j.ArrivalId);
// ... Same thing for operator ...
base.OnModelCreating(builder);
}
}
Edit: To address your above comment about the cascade delete, you can add .WillCascadeOnDelete(false) after .HasForeignKey() and that might help (although you'll then have to delete Journey records manually)
Add the folowing attributes on your navigation properties :
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
[ForeignKey("OperatorID")]
public virtual Operator Operator { get; set; }
[ForeignKey("DepartureID")]
public virtual Station DepartureStation { get; set; }
[ForeignKey("ArrivalID")]
public virtual Station ArrivalStation { get; set; }
}
And of course you need to regenerate your database in order to apply the new configuration.
Hope this will help.

Resources