Element search for Derived Element fields attributes doesn't find any

Posts   
 
    
TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 08-Nov-2018 11:59:51   

Running this Element search returns entries for AttributesSourceField column but not for the Attributes column despite there being System.ComponentModel.DataAnnotations.DisplayAttribute on both entity and Derived Element fields.

public class QueryRunner {
    private const string SqlServerSqlClientDriverID="2D18D138-1DD2-467E-86CC-4838250611AE";
    private const string OracleOdpNetDriverID="A8034CB0-319D-4cdd-BC7D-1E7CFDBA3B74";

    /// <summary>The method which will perform the search</summary>
    /// <param name="p">The project object to search</param>
    public IEnumerable Search(Project p) {  
    var rootDerivedElements = p.DerivedModels.SelectMany(dm => dm);
    var DocumentFieldDefinitions= rootDerivedElements.SelectMany(r => r.GetAllEmbeddedDocuments()).SelectMany(e => e.Fields)
          .Union(rootDerivedElements.SelectMany(r => r.Fields));
    return from f in DocumentFieldDefinitions
        where f.Source is EntityFieldSource
            let SourceField = (f.Source as EntityFieldSource).SourceField
    select new {f.FullName,
    f.Source,
         MSDescription = f.OutputSettingValues.CustomProperties.ContainsKey("MS_Description") ? f.OutputSettingValues.CustomProperties["MS_Description"].ToString() : string.Empty,
            MSDescriptionSourceField = SourceField.OutputSettingValues.CustomProperties.ContainsKey("MS_Description") ? SourceField.OutputSettingValues.CustomProperties["MS_Description"].ToString() : string.Empty,
            Attributes = string.Join(",", f.GetAttributes(p)),  
            AttributesSourceField = string.Join(",", SourceField.GetAttributes(p)),
    f.Source.SourceType,SourceField};
                    }
}

Why is that?

V5.4.4 16-Oct-2018

Jeremy Thomas
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 08-Nov-2018 20:05:59   

Try this out:

Attributes = string.Join(",", f.GetAttributes(p.DerivedModels.SingleOrDefault())), 

As the GetAttributes() method accepts a ITargetFrameworkHolder which can be a derived model or the project, in this case it's the derived model, which is holding the attributes.

TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 09-Nov-2018 01:06:08   

Thanks - that worked - apart from the single - this works now:

    var query = from dm in p.DerivedModels
                from rootDerivedElements in dm
                from embeddedDerivedElements in rootDerivedElements.GetAllEmbeddedDocuments().DefaultIfEmpty()
                from f in rootDerivedElements.Fields.Union(embeddedDerivedElements == null ? Enumerable.Empty<DocumentFieldDefinition>() : embeddedDerivedElements.Fields)
                where f.Source is EntityFieldSource
                let SourceField = ((EntityFieldSource)f.Source).SourceField
                select new
                {
                    f.FullName,
                    f.Source,
                    MSDescription = f.OutputSettingValues.CustomProperties.ContainsKey("MS_Description") ? f.OutputSettingValues.CustomProperties["MS_Description"].ToString() : string.Empty,
                    MSDescriptionSourceField = SourceField.OutputSettingValues.CustomProperties.ContainsKey("MS_Description") ? SourceField.OutputSettingValues.CustomProperties["MS_Description"].ToString() : string.Empty,
                    Attributes = string.Join(",", f.GetAttributes(dm)),
                    AttributesSourceField = string.Join(",", SourceField.GetAttributes(p)),
                    f.Source.SourceType,
                    SourceField
                };
    return query.Distinct();
Jeremy Thomas
TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 18-Dec-2018 09:04:09   

For what it's worth here is a method which syncs up (adds if missing - does not change existing) display attributes between entities and derived models. It saves manually adding display attributes when creating derived models. Element type Enumerable.

foreach (var dm in p.DerivedModels)
    {
        var derivedElementsFields = from rootDerivedElements in dm
                                    from embeddedDerivedElements in rootDerivedElements.GetAllEmbeddedDocuments().DefaultIfEmpty()
                                    from f in rootDerivedElements.Fields.Union(embeddedDerivedElements == null ? Enumerable.Empty<DocumentFieldDefinition>() : embeddedDerivedElements.Fields)
                                    where f.Source is EntityFieldSource
                                    select f;
        foreach (var field in derivedElementsFields)
        {
            var sourceField = (field.Source as EntityFieldSource).SourceField;
            var fAttributes = sourceField.OutputSettingValues.GetFilteredAttributes(p);
            var dmfAttributes = field.OutputSettingValues.GetFilteredAttributes(dm);
            var list = new List<SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>>();
            var hasDisplay = false;
            for (int x = 0; x < dmfAttributes.Count; x++)
            {
                var attribute = dmfAttributes[x];
                if (attribute.Contains("Display("))
                 {
                    hasDisplay = true;
                    break;
                }
                list.Add(new SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>(attribute, null));
            }
            if (hasDisplay)
            {
                var fieldAttributeList = new List<SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>>();
                hasDisplay = sourceField.ContainingElement.Name == "Occurrence";
                foreach (var fieldAttribute in fAttributes)
                {
                    if (fieldAttribute.Contains("Display("))
                    {
                        hasDisplay = true;
                        break;
                    }
                    fieldAttributeList.Add(new SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>(fieldAttribute, null));
                }
                if (!hasDisplay)
                {
                    foreach (var dmFieldDisplayAttribute in dmfAttributes.Where(a => a.Contains("Display(")))
                        fieldAttributeList.Add(new SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>(dmFieldDisplayAttribute, null));
                    sourceField.OutputSettingValues.SetAttributesWithRules(fieldAttributeList);
                }
            }
            else
            {
                var countBefore = list.Count;
                foreach (var fieldDisplayAttribute in fAttributes.Where(a => a.Contains("Display(")))
                    list.Add(new SD.Tools.Algorithmia.GeneralDataStructures.Pair<string, SD.LLBLGen.Pro.ApplicationCore.Configuration.SettingRule>(fieldDisplayAttribute, null));
                if (countBefore < list.Count)
                    field.OutputSettingValues.SetAttributesWithRules(list);
            }
            yield return new
            {
                field.FullName,
                field.Source,
                Attributes = string.Join(",", field.GetAttributes(dm)),
                SourceAttributes = string.Join(",", sourceField.GetAttributes(p)),
            };
        }
    }
Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 18-Dec-2018 10:19:56   

Thanks for sharing simple_smile

Btw, is the rule system too restricted for these? The rule system is meant to make this automatic. (and if there's a DTO model on an entity model, wouldn't it be the case that attributes are defined only on the DTO elements? Just asking to see whether we should add a feature to copy attributes)

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 18-Dec-2018 11:23:40   
  • We have an 'AuditorToUse' which writes human friendly field level change logs to tables in the DB for some (usually root aggregate) entities, for these we want the localized UI labels to appear in the log rather than than the field name.
  • We also have a Data Dictionary UI in our product which enables 'power' users to make a connection between labels on the screen and DB fields.
  • We always use viewmodels/DTOs i.e. we never bind entities to UI.
  • We might have multiple dtos from an entity e.g. a grid model and a viewmodel (for editing)
  • We have unit tests that check every public property on the view models have a either a display attribute or a [ScaffoldColumn(false)] to indicate it's not bound in the UI.
  • In the last year or so we have switched to making our DTO's in the designer and are largely happy with that but probably the biggest downside of that compared to handwriting DTO's is that adding display attributes is more painful than in code. Given the above automating adding the display attributes from the entities is a time saver and yes would be great if the designer could do it.

I've never used the rule system.

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 18-Dec-2018 15:37:57   

Looking at that, I think you should really look into the rule system for attributes/interfaces/namespaces. You can define attributes with macros in the project settings for elements, and assign rules when the designer should add them to an element. E.g. if you want to assign an attribute 'DisplayAttribute' to all string fields which are not nullable, you can do so with a single attribute definition. All elements you have in the project which match the rule will get the attribute, and new ones will automatically get them too.

This makes things very easy for the bulk of the attributes to add. You then have to add only the few exceptional ones which are not applicable with a rule.

Please get back to me if the rule system isn't sufficient and you have to fall back to lots of UI clicking for adding attributes as I agree it's not designed to add lots of attributes manually: the bulk of the attributes are better assigned through rules, and the manual ones should be a small set.

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 21-Dec-2018 12:37:44   

The display attributes are different for each property e.g.

    /// <summary> DTO class which is derived from the entity 'FCA.Action'.</summary>
    [Serializable]
    public partial class ActionViewModel
    {
        /// <summary>Gets or sets the Action field. Derived from Entity Model Field 'FCA.Action.Action'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Form_ActionText")]
        [Required(ErrorMessageResourceType = typeof(FCAStrings), ErrorMessageResourceName = "RA_Action")]
        public System.String Action { get; set; }
        /// <summary>Gets or sets the ActionID field. Derived from Entity Model Field 'FCA.Action.ActionID'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Label_ActionId")]
        public System.Int32 ActionID { get; set; }
        /// <summary>Gets or sets the ActionNo field. Derived from Entity Model Field 'FCA.Action.ActionNo'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Form_Action")]
        public System.String ActionNo { get; set; }
        /// <summary>Gets or sets the ActionStatus field. Derived from Entity Model Field 'FCA.ActionStatus.ActionStatusText (ActionStatuses)'</summary>
        [Display(ResourceType = typeof(Labels), Name = "TableHeader_State")]
        public System.String ActionStatus { get; set; }
        /// <summary>Gets or sets the ActionStatusID field. Derived from Entity Model Field 'FCA.Action.ActionStatusID (FK)'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Label_ActionStatus")]
        [Required(ErrorMessageResourceType = typeof(FCAStrings), ErrorMessageResourceName = "RA_ActionStatus")]
        public Nullable<System.Int16> ActionStatusID { get; set; }
        /// <summary>Gets or sets the ActionType field. Derived from Entity Model Field 'FCA.ActionType.ActionType (ActionType)'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Label_ActionType")]
        public System.String ActionType { get; set; }
        /// <summary>Gets or sets the ActionTypeID field. Derived from Entity Model Field 'FCA.Action.ActionTypeID (FK)'</summary>
        [Display(ResourceType = typeof(FCALabels), Name = "Label_ActionType")]
        [RequiredValue]
        public Nullable<System.Int32> ActionTypeID { get; set; }
Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 22-Dec-2018 08:34:48   

Ok, tho you could get very far with macros and rules (as the field name is in the value passed to the attribute). Then for the ones which need a custom one, e.g. the ActionNo field here, you can disable the inherited attribute and add the custom one. This might save considerable.

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 618
Joined: 25-Oct-2005
# Posted on: 04-Jan-2019 06:55:26   

That would work in cases like this where we appear to have a bit of a convention going on i.e. Label_FieldName

[Display(ResourceType = typeof(FCALabels), Name = "Label_ActionType")]
public System.String ActionType { get; set; }

but for it really to save time that would have to be the norm but if it was universal then we could to labels by convention (as we have done in the distant past) rather than using display attributes which would make this moot.

What I've done with copying display attributes from entity fields to derived models fields seems to be the most efficient for us. One case it doesn't cover is fields on a related entity e.g. staff member name when an entity has FK to a staff member (they typically have several) - in that case the display attribute is the same as that on the FK field so maybe there is something that can be done there.

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 04-Jan-2019 10:24:27   

Thanks for the feedback simple_smile We'll see what we can do in a future version to make this easier simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 29-May-2019 10:49:40   

In v5.6 we added the ability to modify attributes/additional interfaces/namespaces on multiple elements in bulk using the tool that's already there but currently only used for settings: the bulk element setting manipulator. This way you can select a large number of fields, and add/change/remove attributes (or settings etc.) for all of them in 1 go.

Frans Bouma | Lead developer LLBLGen Pro