[CONNECT C++] Using a WhereCriterion to filter out schemas

I'm looking at elements that have XAttributes on them. I can create a ECQuery to find instances on an element. When I look at what is found, it is not uncommon to find multiple Classes on an element. For example:

[DgnElementSchema:LineElement]
[DgnCustomItemTypes_SRSFeature:Air__x0020__Tunnel]

I'm interested in the 'SRSFeature' XAttributes. Is it possible to (and how would you) set up a WhereCriterion to restrict the ECQuery to return only ones from the 'DgnCustomItemTypes_SRSFeature' schema? USe CreateStringFilter()?

Bruce

  • I'm looking at elements that have XAttributes on them
    [DgnCustomItemTypes_SRSFeature:Air__x0020__Tunnel]

    Your question is about Item Types and you want to filter on the property values of an Item Type instance?  Are XAttributes relevant?

    Query Scope

      using namespace Bentley::DgnPlatform;
      FindInstancesScopeOption option (DgnECHostType::Element);
      //  In entire DGN file
      //scope  = FindInstancesScope::CreateScope (*Utilities::GetActiveDgnFile  (), option);
      //  In active model
      Bentley::DgnPlatform::FindInstancesScopePtr scope  = FindInstancesScope::CreateScope (
    *ISessionMgr::GetActiveDgnModelP (),
    option);

    Compose Query

    bool   ECQueryFactory::ComposeCriteria (ECQueryPtr query,
      WCharCP        property,
      WCharCP        filter)
    {
      using namespace Bentley::DgnPlatform;
      bool     composed   { false };
      if (query.IsValid())
      {
        WhereExpressionPtr  expression     { WhereExpression::CreatePropertyExpression (property) };
        const bool&         IfMatch        { true };
        const bool&         CaseSensitive  { true };
        WhereCriterionPtr   where;
        WhereCriterion::StringFilterError   error;
        if (RegEx::IsRegEx (filter))
        {
          where = WhereCriterion::CreateRegexFilter (&error, *expression, filter, !CaseSensitive);
        }
        else
        {
          where = WhereCriterion::CreateStringFilter(&error, *expression, filter, IfMatch, !CaseSensitive);
        }
        }
        if (!where.IsValid () && WhereCriterion::STRING_FILTER_ERROR_InvalidSyntax == error)
        {
          // CreateStringFilter syntax error filter= filter
        }
        else
        {
          query->SetWhereCriterion (*where);
          composed = query.IsValid ();
        }
      }
      return composed;
    }

    Execute

    DgnECInstanceIterable      ECQueryManager::FindInstances   ()
    {
      return ECQueryFactory::FindInstances   (scope_,
                          query_,
                          propertyName_,
                          strFilter_.c_str (),
                          nullptr);
    }

    Thanks to  for guiding me through that jungle.

     
    Regards, Jon Summers
    LA Solutions

  • Are XAttributes relevant?

    Yes. I have models that originated in V8 that have XAttributes added to some elements using XMLInstanceAPI::Native. That API is no longer available in CONNECT. When You examine one of these elements, in the Properties dialog they show up as ItemTypes.

    Now I need to (in CONNECT) report on these XAttributes-turned-ItemTypes. I know I can scan the model, and extract ALL the element's ECClasses, but that returns not only the Item Types, but all the 'other' XAttributes (like DgnElementSchema). I'm wondering if there is a way to "filter" the ones returned to ONLY those in the schema I'm interested in.

    The ClassName is reported (I assume) in the format Schema::Class. I'd like to find any/all that belong to a specific schema without having to test each Class returned to ensure it's in the schema I want.

    It's my impression that when the class name is returned (e.g. DgnCustomItemTypes_SRSFeature:Air__x0020__Tunnel), that since I have a ECClass object, that the Schema and Class names are not really 'properties' of the Class, so I'm not sure how filter on them. I am able to filter them using AddSearchClass() and providing the schema and class, but I'd like to accept ALL Classes and don't want to have explicitly name each one...If I could supply '*' to get all Classes, that would be perfect, but unfortunately that doesn't work.

  • DgnCustomItemTypes_SRSFeature:Air__x0020__Tunnel

    Item Type Library::Class Name

    Get Named Schema

    Here's a method to get a named schema from the active DGN file...

    bool  SchemaFactory::GetNamedSchema     (ECN::ECSchemaPtr&  pSchema, WCharCP name)
    {
        DgnFileP           pDgnFile  = ISessionMgr::GetActiveDgnFile ();
        const UInt32&      VerMajor  = 1;
        const UInt32&      VerMinor  = 0;
        DgnPlatform::SchemaInfo  schemaInfo  (ECN::SchemaKey (name, VerMajor, VerMinor), *pDgnFile);
        DgnECManagerR      ecMan     = DgnPlatform::DgnECManager::GetManager ();
        pSchema   = ecMan.LocateSchemaInDgnFile (schemaInfo,
                ECN::SchemaMatchType::SCHEMAMATCHTYPE_LatestCompatible);
      return pSchema.IsValid ();
    }
    

    Create Query for Item Type

    Create a query for a named Item Type class...

    ECQueryPtr    ECQueryFactory::CreateQueryForItemType  (WCharCP    itemTypeName)
    {
      ECQueryPtr            ecQuery;
      ItemTypeLibraryMgr    libMgr  (L"library name");
      bool                  found  { libMgr.ExistsInActiveFile () };
      if (found)
      {
        ItemTypeLibraryPtr  pLib  { libMgr.Get () };
        ItemTypeP           itemType = pLib->GetItemTypeByName(itemTypeName);
        if (nullptr == itemType)
        {
          // Unable to get Item Type  itemTypeName
        }
        else
        {
          // Note reliance on internal library and class names
          WString  internalName (ItemTypes::GetInternalName (itemType));
          ecQuery  = CreateQuery (pLib->GetInternalName (), internalName.c_str ());
        }
      }
    
      return ecQuery;
    }
    

    Internal Class Names

    Many of the EC interfaces require a class's internal name rather than the name that you & I would expect.  This article discusses internal names and how to obtain them.

     
    Regards, Jon Summers
    LA Solutions

  • Is it not possible to query base on library name alone? For example using a SQL like syntax

    If only that were so!

    I don't understand the concepts or design that went into the EC query language.  I find it hard to use and suspect that I am not alone.  Here are some other paths that Bentley Systems might have considered... 

    1. If data are stored as XML fragments  and schemas are XML, then why not use the XML query language XQuery?
    2. Comments in the API (ECQuery struct reference) state that EC queries are analogous to SQL.  That analogy is stretched rather thin: Why not make them exactly like SQL by incorporating a domain specific language (DSL) compiler?
    3. Why not use a LINQ data source to convert query statements into an ECQuery?

    Instead, we have a proprietary query API that is poorly documented.  It's a combination of classes and text expressions: classes are formally defined and have the usual terse documentation; expressions are not formally defined and have no documentation.  Examples are sparse.

    And why are Report queries different and also undocumented?

     
    Regards, Jon Summers
    LA Solutions

  • Hi John,

    I guess it goes a bit beyond scope of this discussion, but anyway... ;-)

    I don't understand the concepts or design that went into the EC query language.  I find it hard to use and suspect that I am not alone.

    That's probably true. But I guess it wise to split EC query "language" and how it's implemented in API.

    Here are some other paths that Bentley Systems might have considered...

    That's a bit unfair in my opinion, because you want to evaluate technology invented (I guess) more than 10 years ago with pretty complex structure of features and priorities from today perspective. The then decisions can be decided not best easily in such position.

    If data are stored as XML fragments  and schemas are XML, then why not use the XML query language XQuery?

    XQuery focuses unstructured data and it's not context-sensitive tool. Which does not mean XQuery (and XPath) is not seriously powerful tool, but ECQuery is designed to work in defined EC Schema context, which I guess can be hardly implemented into XQuery.

    Why not make them exactly like SQL by incorporating a domain specific language (DSL) compiler?

    ECSQL is available already in iModelJS API. It's all about steady development of the whole technology and implementing new features and tools, where some are not backward compatible (e.g. some changes in ECSchemas in PowerPlatform products and iModelJS).

    Why not use a LINQ data source to convert query statements into an ECQuery?

    I guess LINQ did not exist when EC technology was invented ;-)

    But actually ... nobody protect you from implementing own LINQ extension to work with geometry, standard properties and also custom structures defined in EC schemas. But it sounds like long-term complex task.

    Instead, we have a proprietary query API that is poorly documented.

    The documentation is the biggest problem in my opinion. Even not well structured API can be used efficiently when documented properly. The opposite situation is much worse.

    With regards,

      Jan

  • Hi Bruce,

    If you only want to find a specific [ECShema:ECClass] on an element, I think it doesn't need to use WhereCriterion. Below is an example which can get [BaseElementSchema:MstnVolume] on an element.

    void getVolumeByEC(WCharCP unparsed)
    {
    	ElementId id = wcstol(unparsed, NULL, 10);
    	ElementHandle eh(id, ACTIVEMODEL);
    	if (!eh.IsValid())
    	{
    		mdlDialog_dmsgsPrint(L"invalid element handle");
    		return;
    	}
    	DgnFileP dgnFile = ISessionMgr::GetActiveDgnFile();
    	SchemaInfo schemaInfo(ECN::SchemaKey(L"BaseElementSchema", 1, 0), *dgnFile);
    	DgnECManagerR ecMan = DgnECManager::GetManager();
    	ECN::ECSchemaPtr pSchema = ecMan.LocateSchemaInDgnFile(schemaInfo, ECN::SchemaMatchType::SCHEMAMATCHTYPE_LatestCompatible);
    	if (pSchema.IsNull())
    	{
    		mdlDialog_dmsgsPrint(L"Can't find BaseElementSchema schema");
    		return;
    	}
    	ECN::ECClassCP pVolClass = pSchema->GetClassCP(L"MstnVolume");
    	DgnElementECInstancePtr pInstance = ecMan.FindInstanceOnElement(eh, *pVolClass, true); //polymorphic=true is important
    	if (pInstance.IsNull())
    	{
    		mdlDialog_dmsgsPrint(L"pInstance is null");
    		return;
    	}
    	WString strVal;
    	pInstance->GetValueAsString(strVal, L"SurfaceArea", false, 1);
    	mdlDialog_dmsgsPrint(strVal.GetWCharCP());
    	pInstance->GetValueAsString(strVal, L"Volume", false, 1);
    	mdlDialog_dmsgsPrint(strVal.GetWCharCP());
    }

    An example of using WhereCriterion is as below:

    WhereCriterionPtr wh = WhereCriterion::CreatePropertyComparison(propertyName, WhereCriterion::EQ, propertyValue);
    myQuery->SetWhereCriterion (*wh);
    myResult = DgnECManager::GetManager().FindInstances(*scope, *myQuery);