Derived DTO projection expression and default values

Posts   
 
    
rbrown
User
Posts: 33
Joined: 11-Jun-2008
# Posted on: 23-Feb-2023 22:23:21   

Hi, long time LLBLGen user. Currently looking into the derived DTO entities and persistence logic to see if we can use these for a Web API. I have a couple of questions:

Question 1. I've added a custom partial class to one of my DTO's, with custom property and a helper method in the persistence class to calculate the property value using the original entity with its prefetched related collection. This looks at the related collection and if it finds a particular child value it sets the custom property accordingly, otherwise it leaves the custom property null. I tried calling my custom method within the // __LLBLGENPRO_USER_CODE_REGION_START region of the Persistence class. However, I've found that the prefetched records are not wired up in that context. However, if I put the logic inline within that region, it works correctly. Why is this, and is there a way to make it work?

My custom partial class for the DTO:

    public partial class TagInfo
    {
        [DataMember]
        public string SourceParameterMetaTagPmSiteTag { get; set; }
    }

My custom partial class for the Persistence object:

    public partial class TagInfoPersistence
    {
        public static string GetMetaTagPMSiteTag(ReadingAverageDataTagEntity tag)
        {
            var tagValue = tag?.SourceParameter?.SourceParameterMetaTag?
                .FirstOrDefault(t => t.TagName == "PMSiteTag")?.TagValue;

            return tagValue;
        }
    }

My prefetch and query:

            var prefetch = new PrefetchPath2(EntityType.ReadingAverageDataTagEntity);
            prefetch.Add(ReadingAverageDataTagEntity.PrefetchPathSourceParameter).SubPath.Add(SourceParameterEntity.PrefetchPathSourceParameterMetaTag);

            List<TagInfo> results;
            using (var adapter = DataAccessAdapterFactory.GetDataAccessAdapter())
            {
                var metaData = new LinqMetaData(adapter);

                var q = (from c in metaData.ReadingAverageDataTag
                         where c.SourceParameter.ParameterName.Contains("PM")
                         && c.SourceParameter.SourceParameterMetaTag.Any(mt => mt.TagName == "PMSiteTag")
                         select c).WithPath(prefetch).ProjectToTagInfo();
                results = q.ToList();
            }

A snippet of the projection expression generated by LLBLGen with my custom code in the USER CODE region:

            System.Linq.Expressions.Expression<Func<AirVision.Model.EntityClasses.ReadingAverageDataTagEntity, Glims.DtoClasses.TagInfo>> mainProjection = p__0 => new Glims.DtoClasses.TagInfo()
            {
                IntervalName = p__0.ReadingAverageInterval.IntervalName,
                ParameterEnabled = p__0.SourceParameter.Enabled,
                ParameterName = p__0.SourceParameter.ParameterName,
                SiteEnabled = p__0.SourceParameter.SourceSite.Enabled,
                SiteName = p__0.SourceParameter.SourceSite.SiteName,
                SiteTimeZoneAbbreviation = p__0.SourceParameter.SourceSite.UtilityTimeZone.TimeZoneAbbreviation,
                SourceParameterDataTypeId = p__0.SourceParameter.SourceParameterDataType.SourceParameterDataTypeId,
                SourceParameterId = p__0.SourceParameterId,
                SourceParameterTemplateId = p__0.SourceParameter.SourceParameterTemplate.SourceParameterTemplateId,
                SourceSiteId = p__0.SourceParameter.SourceSite.SourceSiteId,
                TimeInterval = p__0.ReadingAverageInterval.TimeInterval,
                // __LLBLGENPRO_USER_CODE_REGION_START ProjectionRegion_TagInfo 

                // calling the method here does not work <===========================
                //SourceParameterMetaTagPmSiteTag = GetMetaTagPMSiteTag(p__0),

                // Custom properties have to be done inline, the entity is not prefetched when
                // passed into a function
                SourceParameterMetaTagPmSiteTag = p__0.SourceParameter.SourceParameterMetaTag.FirstOrDefault(t => t.TagName == "PMSiteTag").TagValue,

                // __LLBLGENPRO_USER_CODE_REGION_END 
            };

As pointed out in the code comments above, if I inline the metaTag lookup within the expression it works... the prefetched entities are there and available for my FirstOrDefault query. However, if I try to call my GetMetaTagPMSiteTag() function there, the prefetched entities are NULL.

Why is this? Is there a way to call custom functions in this expression? This is obviously a trivial example, so inlining it is totally fine. However, it would be nice to know if this is an option for more complex scenarios.

Question 2. (This is a different DTO/Entity). Our database tables have a few fields which are set via defaults. E.g. the primary key (GUID) and ModifiedOn (datetime) which are non-nullable but are set by default constraints in SQL Server. Normally with our Entities, we simply do not set these fields when doing an insert, so we get the db default values refetched upon save. However, with the DTO persistence object, I noticed it blindly sets ALL properties within the UpdateFromXYZ(entity, dto) method. This causes problems since it wants to assign these fields to the entity, which causes them to override the defaults defined in SQL server. Is there a way to modify this so that it doesn't blindly assign these fields? Would I need to modify the templates for these, and if so, can you point me in the right direction? I've done some custom templates in the past, so I'm somewhat familiar but any guidance is appreciated.

Thanks!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 25-Feb-2023 10:20:19   

simple_smile Answer to question 1: The linq expression is a projection which is converted to a projection in the SQL query. The method call you placed there relies on an instantiated object, but ... that object isn't there yet, as the expression is ran on the DB. Your inline variant works as it results in a SQL query part or a nested query. This is the same as when you specify a method call in a normal linq query (the projection methods are resulting in linq queries): that isn't run till the data coming back from the database is being consumed but not after that.

Answer to question 2: The templates for persistence for DTOs check if a field is readonly and if it is, it'll skip it. However it doesn't know about defaults. If you don't set these fields in .net code, you should mark them as ReadOnly in the entity (so open the entity in the designer, go to the fields tab and check the ReadOnly field).

If you have a lot of entities you have to set the fields for, you can do this in bulk by using the ElementSearch panel (Ctrl-F3) which allows you to write C# code that can consume (and thus modify) the elements in the project.

e.g. to get all entities as a result which have ModifiedOn field, use:

return p.EntityModel.Vertices.Where(e=>e.Fields.Any(f=>f.Name=="ModifiedOn"));

You can then instead of returning the set, first enumerate it and set these fields to ReadOnly.

Frans Bouma | Lead developer LLBLGen Pro
rbrown
User
Posts: 33
Joined: 11-Jun-2008
# Posted on: 06-Mar-2023 23:18:06   

Thanks for the answers! For #1 I figured it was something like that.. I appreciate the explanation. For #2, I've just omitted the ModifiedOn from the DTOs since they didn't really need it. I'll re-evaluate later if it turns out we need those.