DTO nested types...

Posts   
 
    
JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 19-Jun-2017 03:54:16   

I have a class called Snippet, linked to a table of SnippetContent.

If I generate DTOs from this (in LLBLGen 5.2), I get the DTO Snippet object, but it seems to insist on creating a nested type of SnippetTypes.SnippetContent.

As this is a DTO, and the nested type is identical to the instances of the root one, is there any way in the designer to override this and avoid creation of duplicate types? I.e. tell the designer that the collection of SnippetContent objects are indeed SnippetContent objects, and not a lightweight version?

The main issue I have is that I get naming conflicts on the client-side generator, because it includes both namespaces, meaning every (auto-generated) instance has to be manually updated.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Jun-2017 09:12:59   

JSobell wrote:

I have a class called Snippet, linked to a table of SnippetContent.

If I generate DTOs from this (in LLBLGen 5.2), I get the DTO Snippet object, but it seems to insist on creating a nested type of SnippetTypes.SnippetContent.

As this is a DTO, and the nested type is identical to the instances of the root one

The last sentence is something I don't understand. The nested type is not identical to the root.

, is there any way in the designer to override this and avoid creation of duplicate types? I.e. tell the designer that the collection of SnippetContent objects are indeed SnippetContent objects, and not a lightweight version?

The main issue I have is that I get naming conflicts on the client-side generator, because it includes both namespaces, meaning every (auto-generated) instance has to be manually updated.

Please give an dedicated example as I don't really understand what you mean. The nested types are there to avoid name clashes with other types, as they're defined inside namespaces.

Frans Bouma | Lead developer LLBLGen Pro
JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 20-Jun-2017 14:02:48   

Sorry, that does read a bit strangely. What I mean is that the SnippetContent entity and associated DTO is at the root namespace. Then the DTO is generated for a Snippet it contains a collection of IList<SnippetType.SnippetContent> objects, instead of IList<SnippetContent>.

I realised I can rename the SnippetType.SnippetContent to be SnippetType.SnippetContentInner in the designer to avoid naming conflicts in the API, but it would be handy to have a way of stating that Snippet.Contents was indeed a collection of IList<SnippetContent>

You're wondering how there could be a naming conflict? Well the generator for the client-side solution creates the client-side DTOs, but their code has Using DTO; Using DTO.SnippetType; at the top, so any reference to 'SnippetContent' is ambiguous, and I have no control over that generator. For now I've renamed the nested types, but I have to double up everything because in reality they are both the same type.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-Jun-2017 11:11:33   

If there are multiple entities A and B having a relationship with the entity C, then 'C' will end up in both the DTO ADto and BDto, but they're not the same: you can have different fields in the CDto related to ADto as you have in the CDto related to BDto. ADto owns the elements it relates to, so its own CDto. BDto owns its own elements too, so also its own CDto. You might decide to add additional related elements to BDto.CDto, which doesn't affect ADto.CDto, or denormalize fields from ADto.CDto into ADto, which doesn't affect BDto.CDto.

Due to this flexibility, the types are redefined, because they are different types. It's not a simple 1:1 reflection of the entity model, it's a derived model, you can change things, e.g. denormalize fields so they're no longer in e.g. 'Customer' but now part of the related element 'Order'

JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 06-Jul-2018 01:00:21   

I lost this thread, and have just been hitting the same issue in a currentproject.

Yes, I'm aware of the fact the child entities could be different, and I understand why the system makes the child entities scoped to the parent, but it's a PITA in daily usage. Perhaps there's a more 'LLBLGen oriented approach' ?

I have a CompanyEntity and a DtoCompany I have an EmployeeEntity and a DtoEmployee I'm using Linq, and I want a company and all its employees. I have several manipulation functions in my business logic that take a DtoEmployee. So I say something like


var sgwbv = db.Company.WithPath(p => p.Prefetch<Employee>(c => c.Employees)
    .Single(c => c.CompanyId == 123)
    .ProjectToDtoCompany;

The DtoCompany doesn't know about the child DtoEmployees, so I create a new DtoCompanyWithEmployees from the Company Entity and include the child Employees.

So now I say:


    .ProjectToDtoCompanyWithEmployees;

But the returned DtoCompanyWithEmployees.Employees collection are not of a compatible type to pass to the business layer, even though it's structure is identical.

How would you recommend handling this situation? I don't really want to have to clone every DtoCompanyWithEmployees.DtoEmployee to a DtoEmployee. Is there an alternative straightforward way of doing this?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 06-Jul-2018 08:00:05   

JSobell wrote:

How would you recommend handling this situation? I don't really want to have to clone every DtoCompanyWithEmployees.DtoEmployee to a DtoEmployee. Is there an alternative straightforward way of doing this?

It's not a straight-forward way to achieve this. In your situation, I normally see three options:

A. Extend classes 1. Create EmployeeDto as derived model. 2. Create CompanyDto as derived model, without the inner employee collection. 3. Generate code 4. Create a partial class of CompanyDto to extend the properties and add a collection of employees:

namespace MyProject.Common.DtoClasses
{
    public partial class CompanyDto
    {
        [DataMember]
        public List<MyProject.Common.DtoClasses.EmployeeDto> Employees { get; set; }
    }
}

You also would have to extend the PojectTo... method to include the child collection in the fetch routine.

B. Use the generated code as is, and use some library like Automapper to match the two instances...

// here employee is of type MyProject.Common.DtoClasses.EmployeeDto.
myCompanyDto.Employees.Add( mapper.Map<MyProject.Common.DtoClasses.CompanyTypes.OrderDto>(employee);

C. If really necessary, you could create your own version of the template SD_DTO_RootDerivedElementInclude and mappings, to override the default template. In there you could do a couple of little changes to generate as you wish. Note: if you do this you need to generate the inner used classes as mandatory. In example above, if you don't generate EmployeeDto, your code compilation will fail.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 06-Jul-2018 13:37:32   

I see your painpoint, but I don't see any other solution indeed. I think what you want is really an entity 1:1 DTO solution while derived models are really derived models, so separate, denormalized models which can be different per DTO and should be treated as such. That's also why there aren't any relationships in the DTO models: the whole nested hierarchy from the root to all the leafs is one type, and even though nested elements are perhaps the same in structure (e.g. you use an Employee derived element nested in two derived elements), they're not the same in meaning as they can version independently (you can denormalize fields in one of them and the other one isn't affected).

ps: you don't need to specify a prefetch path with the linq query if you have nested types in your DTO, so if you have a department DTO and it nests an employee DTO, just fetch the department, the projection will make sure the employee data is fetched in the same way as prefetch paths (using nested queries).

Frans Bouma | Lead developer LLBLGen Pro
JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 08-Jul-2018 15:59:40   

I think the frustrating thing is that simply removing the namespace prefix from the List<ProjectToDtoCompanyWithEmployees.Employee> to be List<Employee> in the persistence and DTO classes resolves the issue, but will be overwritten every time the code is generated.

Perhaps the editor could have the option to specify the child DTO as a link rather than always creating a nested copy of all the attributes? Having the copy available for times when you want a partial child DTO is handy, but for optimisation and ease of generation I find I want the child DTO itself 99% of the time. Honestly, I almost never use the child DTO entities for this reason, and I only use them in the rare case that I have a heavyweight Entity that I only want a subset of fields from.

Perhaps the template could be altered to see if the signature of the child entity is identical to that of the one in the parent namespace, and refer to that one if it is?

Re prefetch paths, I've never tried without, but I just tried it and the Linq query has no idea that the entity needs to populate the child entities, as the projection occurs after the query, so I'm not quite clear what you're suggesting I don't need simple_smile

JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 08-Jul-2018 16:03:01   

Thinking about it, you could almost require that the user provides a DTO for any nested object. If I want a lightweight Employee I should name it MiniEmployee or something, and I could add that as a 'top level' Dto definition, then set the relationship from my Company to be to that entity. That prevents awkward second-instances in different namespaces, and allows the specification of child DTOs that are compatible with the Projection code.

Just a thought.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 09-Jul-2018 07:38:10   

JSobell wrote:

I think the frustrating thing is that simply removing the namespace prefix from the List<ProjectToDtoCompanyWithEmployees.Employee> to be List<Employee> in the persistence and DTO classes resolves the issue, but will be overwritten every time the code is generated.

You still could create a version of the template that override. We don't recommend this as your first option, as mentioned above (it breaks the meaning of DTO's independence, and you have another piece of code to maintain: your template), it's still an option though.

JSobell wrote:

Perhaps the editor could have the option to specify the child DTO as a link rather than always creating a nested copy of all the attributes? Having the copy available for times when you want a partial child DTO is handy, but for optimisation and ease of generation I find I want the child DTO itself 99% of the time. Honestly, I almost never use the child DTO entities for this reason, and I only use them in the rare case that I have a heavyweight Entity that I only want a subset of fields from.

I guess this will go to the considerations for feature requests.

JSobell wrote:

Re prefetch paths, I've never tried without, but I just tried it and the Linq query has no idea that the entity needs to populate the child entities, as the projection occurs after the query, so I'm not quite clear what you're suggesting I don't need simple_smile

The projection is after the query, but the projection determines the 'select' part and that part determine the relations and additional data that needs to be retrieved. In other words, in a well projected DTO, you don't specify prefetchPaths, because the projection itself is in charge of fetching that additional information.

David Elizondo | LLBLGen Support Team
JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 09-Jul-2018 14:02:01   

OK, so I have the following (db is a LinqMetaData instance):


var sg2 = db.SymbolGroup.WithPath(p => p.Prefetch(f => f.BarValues)).Single(s => s.SymbolGroupId == 1).ProjectToSymbolGroupWithBarValues();

This populates the SymbolGroupWithBarValues as expected (although the BarValues are of type SymbolGroupWithBarValues.BarValues... PITA)

Are you suggesting I can say


var sg2 = db.SymbolGroup.Single(s => s.SymbolGroupId == 1).ProjectToSymbolGroupWithBarValues();

and get the BarValues populated?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 09-Jul-2018 16:45:08   

With one difference: .Single() returns a single entity, so ideally you want an IQueryable, so I'd do

var sg2 = db.SymbolGroup.Where(s => s.SymbolGroupId == 1).ProjectToSymbolGroupWithBarValues();

as that will effectively put the Select lambda created by the .ProjectTo....() method as the select of the query, and all nested elements are fetched using nested queries in the projection (1 DB query per nested query, same as prefetch paths), and you don't need the prefetch path materialization here.

See this example:


        public static IQueryable<NWMM.Dtos.DtoClasses.CustomerOrder> ProjectToCustomerOrder(this IQueryable<NWMM.Adapter.EntityClasses.CustomerEntity> baseQuery)
        {
            return baseQuery.Select(_projectorExpression);
        }


private static System.Linq.Expressions.Expression<Func<NWMM.Adapter.EntityClasses.CustomerEntity, NWMM.Dtos.DtoClasses.CustomerOrder>> CreateProjectionFunc()
{
    return p__0 => new NWMM.Dtos.DtoClasses.CustomerOrder()
    {
        CompanyName = p__0.CompanyName,
        CustomerId = p__0.CustomerId,
        Orders = p__0.Orders.Select(p__1 => new NWMM.Dtos.DtoClasses.CustomerOrderTypes.Order()
        {
            Employee = new NWMM.Dtos.DtoClasses.CustomerOrderTypes.OrderTypes.Employee()
            {
                FirstName = p__1.Employee.FirstName,
                LastName = p__1.Employee.LastName,
            },
            OrderDate = p__1.OrderDate,
            OrderId = p__1.OrderId,
        }).ToList(),
// __LLBLGENPRO_USER_CODE_REGION_START ProjectionRegion_CustomerOrder 
// __LLBLGENPRO_USER_CODE_REGION_END 
    };
}

(generated with the DTO templates). this is the actual lambda projection in the DTO persistence generated class, and called with the first method in the snipped above, as the lambda is cached in the variable _projectorExpression.

Here the 'Orders' nested set in the Customer DTO is fetched as a nested query, so I don't need to specify the prefetch path for Orders in the linq query fetching the Customer(s), the projection lambda makes sure things are fetched properly as the nested 'Orders' lambda (p__0.Orders.Select(...)) is seen as a nested query and fetched as such.

(as the above code, is equal to:


public static IQueryable<NWMM.Dtos.DtoClasses.CustomerOrder> ProjectToCustomerOrder(this IQueryable<NWMM.Adapter.EntityClasses.CustomerEntity> baseQuery)
{
    return baseQuery.Select(p__0 => new NWMM.Dtos.DtoClasses.CustomerOrder()
    {
        CompanyName = p__0.CompanyName,
        CustomerId = p__0.CustomerId,
        Orders = p__0.Orders.Select(p__1 => new NWMM.Dtos.DtoClasses.CustomerOrderTypes.Order()
        {
            Employee = new NWMM.Dtos.DtoClasses.CustomerOrderTypes.OrderTypes.Employee()
            {
                FirstName = p__1.Employee.FirstName,
                LastName = p__1.Employee.LastName,
            },
            OrderDate = p__1.OrderDate,
            OrderId = p__1.OrderId,
        }).ToList(),
// __LLBLGENPRO_USER_CODE_REGION_START ProjectionRegion_CustomerOrder 
// __LLBLGENPRO_USER_CODE_REGION_END 
    });
}

but we cache the expression as it's expensive to create each time)

Will now answer your suggestion simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 09-Jul-2018 16:55:51   

JSobell wrote:

Perhaps the editor could have the option to specify the child DTO as a link rather than always creating a nested copy of all the attributes? Having the copy available for times when you want a partial child DTO is handy, but for optimisation and ease of generation I find I want the child DTO itself 99% of the time. Honestly, I almost never use the child DTO entities for this reason, and I only use them in the rare case that I have a heavyweight Entity that I only want a subset of fields from.

So in fact a second entity (-like) model derived from the real model, with relationships? Or at the level of code generation where you simply get a code model (in the form of DTOs) like CustomerDTO, OrderDTO, OrderLineDTO, ProductDTO etc. which are 1:1 derived from Customer, Order, OrderLine, Product and the DTOs have associations and collections (like CustomerDTO.Orders is a List<OrderDTO>) and comes with a set of projection lambdas to fetch these DTOs from a query? (which is likely complicated as it's not defined what to fetch in 1 go, i.e. fetching CustomerDTO, does it fetch its OrdersDTOs as well?)

Frans Bouma | Lead developer LLBLGen Pro
JSobell
User
Posts: 145
Joined: 07-Jan-2006
# Posted on: 10-Jul-2018 03:38:45   

At the moment the DTO definition expands, and any related entity is auto-added and shown saying something like "BarValue (Source: BarValue (BarValues))" From this it knows the nested object is at least a subset of a BarValue.

When you then edit the Dto, it has a column "Type" that says the type is "'BarValue' and it's a 'Multi-element set'. Nested inside that is a set (which you can choose to make a subset) of properties from a BarValue. So my thought was that the user could specify the 'Type' on the 'BarValue' to be a 'Dto.BarValue' (rather than the auto-created 'Dto.SymbolGroup.BarValue' it is at the moment), or even allow the user to select any top-level DTO that was created from the linked Entity relationship (so they can choose a pre-defined subset of properties for light-weight entities if they so wish). Honestly though, even allowing restriction to a full DTO of the linked entity would suffice in the vast majority of cases. All of this is really simply a projection issue. The current ProjectSymbolGroupWithBarValues() method auto-generates a list of nested DTO's of type List<SymbolGroup.BarValue> which is structurally identical to a List<BarValue>, but a PITA because it's a child class that's only created by that one method in the whole application.

Please don't see too much depth in my request. The Entity objects are fine, the existing DTO models are excellent too, this is simply a request to find a way of specifying that the child object is a BarValue, so it doesn't need to create an identical copied class definition.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 11-Jul-2018 13:24:47   

Ok simple_smile We'll see what we can do in the future. It will require a different way of how derived models are built in the designer, so it's not that simple (as it currently doesn't share elements which derive from the same entity among containing elements). Thanks for the feedback!

Frans Bouma | Lead developer LLBLGen Pro