How to design DTO classes for nested domain objects? - automapper

I am new to using DTOs.
I have two domain classes:
Category
Product
as follows
public class Category
{
// Properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation properties
public virtual Category ParentCategory { get; set; }
// Foreign key
public int? ParentCategoryId { get; set; }
// Collections
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<Category> Subcategories { get; set; }
}
public class Product
{
// Properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation property
public virtual Category Category { get; set; }
// Foreign key
public int CategoryId { get; set; }
}
I want to use Automapper.
I am not sure how to design DTOs for the above aggregate (graph).
Should CategoryDTO have a collection of type ProductDTO or a collection of type Product?
Should ProductDTO have a CategoryDTO as navigation property or a Category or just an Id to the Category?
Can anyone suggest the code for DTOs?
How should this structure be flattened (if it should) and mapped to domain classes?
Many thanks in advance.

I design my DTOs to be only the data used for a specific controller action for MVC. Typically this means if I have a CategoryController, then I have a CategoryIndexModel, CategoryDetailsModel, CategoryEditModel etc etc. Only include the information you want on that screen. I flatten as much as I can, I don't create child DTOs unless I have a Partial or collection.

Related

Automapper - flattening of object property

let's say I have
public class EFObject
{
public int Id { get; set; }
public int NavId { get; set; }
public NavObject Nav { get; set; }
}
public class DTOObject
{
public int Id { get; set; }
public int NavId { get; set; }
public string NavName { get; set; }
}
My expectation was high, and I thought to my self the built-in flattening should handle this, so my mapping is very simple
CreateMap<DTOObject, EFObject>().ReverseMap();
Unfortunately, converting DTOObject to EFObject does not work as expected because EFObject.Nav is null. Since I used the name NavId and NavName I would expect it to create a new NavObject and set the Nav.Id and Nav.Name accordingly.
My Problem : Is there a feature in Automapper that will allow me to achieve the intended result without having to manually write a rule to create an NavObject when mapping the Nav property?.
Unflattening is only configured for ReverseMap. If you want unflattening, you must configure Entity -> Dto then call ReverseMap to create an unflattening type map configuration from the Dto -> Entity.
as noted by Automapper documentation here

DDD/CQRS, Entity has access to Query, Command?

public class PageRoleService
{
public void SetRoles(Page page, User activeUser)
{
var rb = page.Project.ProjectType.GetRoleFor(activeUser.UserType);
page.RolesForPage.Add(activeUser, rb);
var managers = GetAllManagersOf(activeUser);
foreach (var m in managers)
{
page.RolesForPage.Add(m, rb);
}
}
}
public class Project : Entity
{
public ProjectType ProjectType { get; set; }
public IList<Page> Pages { get; set; }
}
public class Page : Entity
{
public string Name { get; set; }
public Project Project { get; set; }
public IDictionary<User, RoleBehaviour> RolesForPage { get; set; }
}
public class ProjectType : Entity
{
public IQueryProcessor QueryProcessor { get; set; }
public IList<RoleBehaviour> RoleBehaviours { get; set; }
public RoleBehaviour GetRoleFor(USerType userType)
{
var behaviour = return QueryProcessor.Execute(new GetRolesByUserAndProjectTypeQuery() {
ProjectType = this,
UserType = userType
});
// Filter behaviour attributes for project type properties, business rules, etc...
// FilterBehaviour(behaviour);
return behaviour;
}
}
public class GetRolesByUserAndProjectTypeQuery
{
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
}
public class GetRolesByUserAndProjectTypeQueryHandler
{
public Db Db { get; set; }
public RoleBehaviour Execute(GetRolesByUserAndProjectTypeQuery query)
{
return Db.FirstOrDefault(r => r.UserType == query.UserType && r.ProjectType == query.projectType);
}
}
public class RoleBehaviour : Entity
{
public Role ROleForArea1 { get; set; }
public Role ROleForArea2 { get; set; }
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
public IDictionary<string, string> Attributes { get; set; }
}
public enum UserType
{
A,
B,
C,
D
}
public class Role : Entity
{
public IList<string> Permissions { get; set; }
}
I don't use repository, no need data abstraction, I use CQRS for crud operations. (CreateProjectCommand, GetRolesByUserAndProjectTypeQuery, etc..)
Users related a lot of project and page. Users have more than role for each Page Entity and is dynamically created when user (client) request to fetch All projects page or single page item.
My Page Role Service determinates page roles for active user and its managers. My MVC Controller use PageRoleService.
PageRoleService is Application Service or Domain Service or .....?
QueryProcessor in Entity (ProjectType) is invalid approach? How can handle this/their problems without lazy or eager loading?
RoleBehaviour is Entity or Value Object?
PageRoleService is a service or business logic in domain?
I know that I'm some years later, but:
I would put away the base class Entity, because it looks that this are just Dtos returned by the queryhandler (infact GetRolesByUserAndProjectTypeQueryHandler.Execute returns a RoleBehaviour).
Given this, I think that:
PageRoleService is a simple service that completes a Dto, hence it looks a kind of factory
Given that ProjectType here has two different roles (a Dto and Entity, and this is against CQRS), if:
it's a Dto, then use a service/factory/ORM to load extra data on it
it's an Entity, try to load all the data that's needed by it. This because there're great changes that you'll need it on the way to execute your command (great explanation about DDD and entities).
The object has it's own identity? Has it an Id that, even if things will change, remains the same? Looking at it, it looks just a Dto, with nothing really interesting (at business level).
see 1.

How do I create a navigation property that can navigate to more than one entity type?

I have the following in my domain classes ( simplified )
public enum JobType
{
SalesOrder = 1,
StockOrder = 2
}
public class SalesOrder : LoggedEntity
{
public string Name { get; set; } // and other fields
}
public class StockOrder : LoggedEntity
{
public string Name { get; set; } // and other fields
}
public class Job : LoggedEntity
{
public int JobType { get; set; } // jobtype enum
public virtual LoggedEntity LinkedEntity { get; set; }
}
My context is as follows;
public class Context : DbContext
{
public DbSet<Job> Jobs { get; set; }
public DbSet<StockOrder> StockOrders { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
}
When I run the migration i get the error described [here][1] So using an abstract entity appears not to work.
My question was, how do I create a navigation property that can navigate to more than one entity type?
If JobType = SalesOrder then I want to navigate to sales order, if JobType = StockOrder then I want to navigate to stock order.
I wanted to use a Table Per Heirarchy Strategy [see TPH here][2]
The trick is to keep EF oblivious of the LoggedEntity class. Remodel your entities according to this example:
public enum JobType
{
SalesOrder = 1,
StockOrder = 2
}
public abstract class LoggedEntity
{
public int Id { get; set; }
public string Name { get; set; } // and other fields
}
public abstract class BaseOrder : LoggedEntity // New base class for orders!!
{ }
public class SalesOrder : BaseOrder
{ }
public class StockOrder : BaseOrder
{ }
public class Job : LoggedEntity
{
public JobType JobType { get; set; } // jobtype enum
public virtual BaseOrder Order { get; set; }
}
public class Tph2Context : DbContext
{
public DbSet<Job> Jobs { get; set; }
public DbSet<BaseOrder> Orders { get; set; }
}
You will see that the migration creates two tables, Jobs and BaseOrders (name to be improved). Job now has a property Order that can either be a SalesOrder or a StockOrder.
You can query specific Order types by
contex.Orders.OfType<StockOrder>()
And you will notice that EF doesn't know LoggedEntity, because
context.Set<LoggedEntity>()
will throw an exception
The entity type LoggedEntity is not part of the model for the current context.
how do I create a navigation property that can navigate
to more than one entity type?
You cannot do so. atleast not now. navigational properties are way of describing relationship between entities. at most, they represent, some sort of sql relationship. so you cannot alter or define such a relationship on the fly. you have to define it before hand.
Now in order to do that, you have to define separate navigational property for your separate conditions i.e.
public class Job : LoggedEntity
{
public int JobTypeSales { get; set; }
public int JobTypeStock { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
public virtual StockOrder StockOrder { get; set; }
}
and then link them in configuration in modelbuilder through fluent API.
HasRequired(s => s.SalesOrder)
.WithMany()
.HasForeignKey(s => s.JobTypeSales).WillCascadeOnDelete(true);
HasRequired(s => s.StockOrder)
.WithMany()
.HasForeignKey(s => s.JobTypeStock).WillCascadeOnDelete(true);
and
as for your error "Sequence Contains No Elements"
this error comes, when the Linq query that you specified, is using either .First() or .Single(), or .ToList() and query returned no data.
so to avoid it use, .FirstOrDefault() or SingleOrDefault().
obviously with proper null check.

Filling child entity with Entity Framework SqlQuery

I have two entities in 1:n relationship: Category and Product.
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public virtual Product { get; set; }
}
public class context : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
Its possible to load products in every category by Eager loading.
context.Categories.Include(c=>c.Products).ToList()
How can I load products in every category in below query same as Eager loading?
var q = #"
SELECT Categories.*
JOIN Products
ON Category.CategoryId = Products.CategoryId";
var c = context.Categories.SqlQuery(q).ToList();
Its only a simple query. I need to use SqlQuery to execute some queries.
According to this explanation you can't:
the query should be written to ensure that it only returns entities that are really of the requested type
(my emphasis)
So it's only by lazy loading (if enabled) that you can load the Products of the categories after the SqlQuery has run, which will cause n+1 queries.
I don't think it is possible to materialize entities obtained from Sql query if the result contains multiple entity types.

Is instantiating a collection in a domain model considered a good practice?

I see these types of model is many samples online.
public class User
{
public long Id { get; set; }
public string Name{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Is it considered a good practice to instantiate a collection in the constructor like the code below? If so what are the reasons? How about objects in the model?
public class User
{
public User()
{
Products = new List<Product>();
}
public long Id { get; set; }
public string Name{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Well, I would say it depends on the situation, but Products in this case would be filled from the database, via a repository, so most probably ORM of some sort, so no initialization to new List would be needed in the constructor. The meaning of null for Products is indicative that the list isn't loaded yet. On the other hand, let's say that your object must have this collection initialized. For simple objects DDD says constructors are perfectly fine to to these things, but in case of complex objects, move the construction to the Factory.

Resources