I’m currently working on an n-tier web project. After researching into Data Transfer Objects and their benefits we decided to give this pattern a go. Our ASP.NET MVC website does not have direct access to the EF DbContext but instead will use DTOs to send and receive entity data. There will be a service/mapping layer that will convert between DTOs and entity models.
My question is, what is the best way to translate entity model navigation properties into its DTO?
Below is an example of a entity model and its DTO from the project:
Entity Model:
public class Payment
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//Navigation Properties
public virtual PaymentMechanism PaymentMechanism { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
DTO:
public class PaymentDto
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//--------Navigation Properties - Object Ids--------
public int PaymentMechanismId { get; set; }
public ICollection<int> OrderIds { get; set; }
}
As can be seen they are very similar except for the navigation properties. I have changed them to hold integer Ids (of the entities) instead of the entity models. Therefore if the navigation property entities need to be obtained, their Id’s can passed into a service/mapping layer function which will retrieve the entities from then database, map them to DTOs and return the collection. Is this an acceptable way of doing things?
I am new to this area so some of my terminology might not be totally correct but hopefully you’ll understand what I'm getting at. If you need me to clarify or provide additional detail on anything, please let me know.
You can load the DTOs using a projection:
var paymentDtos = context.Payments
.Where(p => p.Amount >= 1000m) // just an example filter
.Select(p => new PaymentDto
{
ID = p.ID,
PaidOn = p.PaidOn,
Amount = p.Amount,
Reference = p.Reference,
PaymentMechanismId = p.PaymentMechanism.ID,
OrderIds = p.Orders.Select(o => o.ID)
})
.ToList();
You have to declare the OrderIds in the dto as IEnumerable<int> though, not as ICollection<int> to make this compile.
I'm not sure if this key collection is really useful. If you want to load the orders later you could do it in a separate service method just based on the ID of the Payment, like so:
public IEnumerable<OrderDto> GetPaymentOrders(int paymentID)
{
return context.Payments
.Where(p => p.ID == paymentID)
.Select(p => p.Orders.Select(o => new OrderDto
{
ID = o.ID,
//etc. mapping of more Order properties
}))
.SingleOrDefault();
}
I'm usually using Automapper for this kind of scenario. I would create a Dto class form my main entity and also Dto's for my navigation property entities, then let Automapper do the mapping automatically, without having to write the mapping code manually.
public class PaymentDto
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//Navigation Properties
public virtual PaymentMechanismDto PaymentMechanism { get; set; }
public virtual ICollection<OrderDto> Orders { get; set; }
}
public class PaymentMechanismDto
{
//properties
}
public class OrderDto
{
//properties
}
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.CreateMap< Payment, PaymentDto >();
Mapper.CreateMap< PaymentMechanism, PaymentMechanismDto >();
Mapper.CreateMap< Order, OrderDto >();
}
}
Related
I am creating a basic rating system where users will enter a movie we have watched together and review it with a rating out of 10. I am struggling with how to get all the data to be retrieve into a view model(needs more work but dev testing currently).
Controller:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
//not much here since I really am that stuck
if (#event == null)
{
return NotFound();
}
return null;
}
ViewModel:
public class EventViewModel
{
public Event Event { get; set; }
public List<Review> Reviews { get; set; }
}
I have models:
Event:
public class Event
{
public int Id { get; set; }
[ForeignKey("Event")]
public int EventTypeID { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
}
EventType:
public class EventType
{
public int Id { get; set; }
public string Type { get; set; }
}
Review:
public class Review
{
public int Id { get; set; }
[ForeignKey("ApplicationUser")]
public int UserID { get; set; }
public int Rating { get; set; }
[ForeignKey("Event")]
public int EventID { get; set; }
}
First, for a view model, this should reflect the data that the view wants to consume rather than containing the entities. Entities should always reflect the data state and that is often more information, and a relational model of the data which the view doesn't really need. As a general rule, an entity should never be passed outside of the scope of the DbContext where it was retrieved. EF does support detached entities but these need to be used with care and generally cause a lot more problems than they are worth in a project.
The goal of a view model is to compact the data that the view needs which improves over the wire performance, and also protects your system from revealing too much about it's data structure.
public class EventViewModel
{
public int Id { get; set; }
public string EventType { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
public ICollection<ReviewViewModel> Reviews { get; set; }
}
public class ReviewViewModel
{
public string UserName { get; set; }
public int Rating { get; set; }
}
Then when you go to select the event and it's reviews, you utilize something called Projection to translate the entities into the view models. This is done manually using Select() or can be automated by leveraging a library like AutoMapper which has a ProjectTo<T>() method.
Ideally your controller should have a DbContext injected, however as a starting point this example just scopes the DbContext in the request:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
using(var context = new YourAppDbContext())
{
var event = await context.Events
.Where(x => x.Id == id)
.Select(x => new EventViewModel
{
Id = x.Id,
EventType = x.EventType.Name,
DateTime = x.DateTime,
EventName = x.EventName,
Reviews = x.Reviews
.Select(r => new ReviewModel
{
UserName = r.User.Name,
Rating = r.Rating
}).ToList()
}).SingleAsync();
return View(event);
}
}
The projection here makes a few assumptions. For instance, if you have an EventType entity for the type of event, we may just want to display the event's Type as a string with the event so there's no need to create an event type view model, just Select the EventType.Name as "EventType" into the EventViewModel. ViewModels can flatten the relational data represented by the entities in this way to only pass the info the view can use rather than everything. The same thing was done to get a user's name for the review (Assuming a User navigation property on the Review) so if you wanted to list reviews you could have a user name, comment, and rating. If not needed, then you could even just use a List<int> for ratings and use x.Ratings.Select(r => r.Rating).ToList() to just get the rating #'s. You can further refine the query if you want to do things like retrieve the most recent 10 ratings or such in the query expression with OrderBy and Take. EF will compose that all down to SQL to only pull back the data necessary to populate the view model. (Faster and less memory used on the server) The advantage of projection is that you can query values from the related entities via their navigation properties as you need in the query expression without having to worry about eager loading /w Include(). EF will work out building a query to populate whatever you extract via Select.
I've search the internet for a few hours now and can't seem to figure out any solutions for myself, or understand some of other similar answers I'm finding.
All I'm trying to do is ignore property from a nested object in my AutoMapper. Here's a small overview of the Models I'm working with (I removed some properties to make them a bit smaller for the purposes of this question).
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class ProductDto
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
public DateTime Created { get; set; }
}
public class CategoryDto
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
}
Basically all I want is my automapper to ignore the Created property coming from the Category class anytime a Product is queried via API. The closest I've to achieving this is by having the whole Category object ignored when queried.
Here is the current mapping configuration for my Product class
public class ProductMapping: Profile
{
public ProductMapping()
{
CreateMap<Product, ProductDto>()
.ReverseMap()
.ForMember(x => x.ProductId, o => o.Ignore());
}
}
I was able to null out the whole object by putting .ForPath(x => x.Category.Created, o => o.Ignore() before .ReverseMap()
I should note that of course the classes and mapper class are distributed through multiple files and the CategoryMapping class looks the same as the ProductMapping. It is removing the Created property, though that is expected.
If anyone can help isolate my issue, or demonstrate a better way to achieve this I am open to suggestions. Till then I will continue trying to figure out this issue. Thanks for any help!
If I understand correctly if you want to ignore the Created field from Category class then you should maybe put the ignore logic when mapping from CategoryDto -> Category or vice versa and mapping from ProductDto <-> remains the same.
CreateMap<Product, ProductDto>()
.ReverseMap()
CreateMap<Category, CategoryDto>()
.ReverseMap()
.ForMember(x => x.Created, o => o.Ignore());
May have answered my own question, but what I had to do was switch the Data type in my ProductDto from Category to CategoryDto. I was under the assumption that Automapper would sort of take care of that itself.
Sorry for that! Thank you to person who took the time to give me an answer aswell!
I'm having some trouble to get into EF Core relationship.
I didn't know how to search it properly, so I've not found what I need, but I got somewhere.
I have these two classes:
Expense:
public class Expense : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public List<ExpenseType> Types { get; set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
ExepenseType:
public class ExpenseType : Entity
{
#region properties
public string Name { get; private set; }
public string Description { get; private set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseTypeIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
During the ToListAsync in ExpenseType, the EF adds the column "expenseId" to the query, but this column does not exist.
My database has three tables, one for each class, and one for the relationship.
(Expense, ExpenseType and Expense_ExpenseType)
By looking for the solution here on StackOverflow I found that I should have a class for the third table.
Here it is:
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them.
So ExpenseType hasn't any Expense.
I'm not sure what I should do now.
Should I Map using optionsBuilder? How?
Should I ReWrite the database?
If you want to create Many-to-Many relationship, you have several options how to do it:
Create additional class how you described. In this case EF will create table and you can get access to get values only from this table.
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
You may don't create class and just describe in the context relationship. Where you will describe everything and EF will create by yourself this table. But from the app you will not see this table. You have to use this variant if you don't want to extend table with additional fields.
modelBuilder
.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
For this relationship you can read more here
But in your case you don't need to use Many-to-Many. That's why if you don't want to add propertie ExpanseTypeId or ExpenseId in your model you can describe it like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Expense>()
.HasMany<ExpenseType>(o => o.Types) //It is your list of expense types.
.WithOne() //Here you can add model for expense. To have an option go back from expense type to expense
.HasForeignKey("ForeignKey");//This key EF will create for you in DB but not in you app model
}
What do you want to use you have to decide. If you have an idea that expense has a lot of expensetypes and each expense type has a lot of expenses. You have to use Many-To-Many how I described.
I think that your main question is "My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them."
so you can do that by creating a nullable foreign key ExpenseTypeId in Expanse class and HashSet of Expanse in ExpeseType class.
Like this:
public class ExpenseType : Entity
{
public ICollection<Expanse> Expanses {get; set;} = new HashSet<Expanse>()
}
public class Expense : Entity
{
public int? ExpanseTypeId {get; set;}
public ExpanseType ExpanseType {get; set;}
}
I need to map DTOs object into my entity object.mapping DTO object into Entity object with one to many relationships it's not working.however when mapping single DTO object to single Entity object its work fine.
Entities -
public class EntityClass
{
[Key]
public int Id { get; set; }
public decimal MonthlyPricing { get; set; }
public virtual IEnumerable<DynamicField> DynamicFields { get; set; }
}
public class DynamicField
{
public int Id { get; set; }
[ForeignKey("Service")]
public int ServiceId { get; set; }
public virtual Service Service { get; set; }
}
My DTO is below ,
public class DTO_Object
{
public int Id { get; set; }
[Required]
public decimal MonthlyPricing { get; set; }
public IEnumerable<DynamicFieldForm> DynamicFields { get; set; }
}
public class DynamicFieldForm
{
public int Id { get; set; }
public int ServiceId { get; set; }
}
my mapping is below,
var config = new MapperConfiguration(cfg => cfg.CreateMap<DTO_Object,
EntityClass>().ForMember(s => s.DynamicFields,o => o.MapFrom(s =>
s.DynamicFields.Select(m => m.Id))));
IMapper imapper = config.CreateMapper();
var service = imapper.Map<DTO_Object, EntityClass>(sourse);
Auto Mapper is not good habit ! It is better that you should build your DBContext manually to link your model's data which are/is encapsulated into the class model.
Again build your Entity Context manually DO NOT USE AutoMapper it is a very
bad habit you can check out this on web resources why is better to write
you Entity context manually.
If you want Entity Data class generate for you you can do it Code First from Database and when you model(s) will be created also Entity DataContext will be created in parallel and with correct mapping then you make correction(Adding Models, Deleting something, Editing).
P.S. For example when i am building something for Enterprise Apps(WebAPI or Web back end) I DO NOT USE AUTO MAPPING. I am coding everything myself.
I'm using .NET Core and EF Core for a web project. I'm struggling how to query a many-to-many releationship. This is what my models look like:
public class Begrip
{
public int ID { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
[Url]
public string URL { get; set; }
public ICollection<BegripCategory> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<BegripCategory> Begrippen { get; set; }
}
public class BegripCategory
{
public int begripId { get; set; }
public Begrip begrip { get; set; }
public int categoryId { get; set; }
public Category category { get; set; }
}
And my Database context:
public class PBBContext : DbContext
{
public PBBContext (DbContextOptions<PBBContext> options)
: base(options)
{
}
public DbSet<PBB.Models.Movie> Movie { get; set; }
public DbSet<PBB.Models.Begrip> Begrip { get; set; }
public DbSet<PBB.Models.Category> Category { get; set; }
public DbSet<PBB.Models.BegripCategory> BegripCategory { get; set; }
protected override void OnModelCreating(ModelBuilder modelbuilder)
{
modelbuilder.Entity<BegripCategory>().HasKey(bc => new { bc.begripId, bc.categoryId });
modelbuilder.Entity<BegripCategory>().HasOne(b => b.begrip).WithMany(bg => bg.Categories).HasForeignKey(bc => bc.begripId);
modelbuilder.Entity<BegripCategory>().HasOne(c => c.category).WithMany(ca => ca.Begrippen).HasForeignKey(cc => cc.categoryId);
}
}
What im trying to do is to return all the "Begrippen" in a JSON result with all the corresponding "Categories", however, I can't figure out how to get the list of "Categories" for them.
Any ideas? Thanks in advance.
EF Core won't load related properties automatically, so you'll need to explicitly do this, but something like the following should do the trick:
var result = context.Begrip
.Include(x => x.Categories)
.ThenInclude(x => x.category);
Note, intellisense doesn't always work on .ThenInclude at the moment, but the code should still compile even if it gets a red underline.
If you're returning this to the view or an API, you'll likely want to map it to a DTO so you don't have to deal with .Categories[0].category.Name etc.
If you need to filter a Many-to-Many relationship describe below I recomend to use a LinQ Enumerable Any method like this:
return result.Where(x => x.Categories.Any(c => c.category == categoryId));
To return a filtered list of entities related by a specific category.
EntityFrameworkCore Relationship query example
Extending #Richard's answer :
I noticed in Visual Studio 2017 15.5.6 when I do following:
return _context.Begrip
.Include(x => x.Categories)
.ThenInclude(y => y.<nothing typed in here yet>)
IntelliSense at first tells me that y if of type ICollection of BegripCategory presenting methods suitable for collections what is confusing especially that when I start typing "category" (in place of "nothing typed in here yet") IntelliSense changes as if we were dealing with only a single instance instead of ICollection
Just a tiny remark, but I hope it will help to save a few minutes time confusion.