The Language Integrated Query or Linq is a .NET Framework component that adds powerful native data querying capabilities to .NET languages.
Here is a contribution by Joel Karr of Environmental Systems Design, Inc on this exciting topic, demonstrating how it can be used to achieve huge performance improvements when analysing large Revit models.
Using LINQ to Query
Here at Environmental Systems Design, www.esdesign.com, we have seen that a simple task in Revit, such as changing a single parameter of an object, can take an immense amount of time when multiplied over thousands of elements in a model. Through the use of LINQ, we have found that we have a wide range of powerful query tools that can help us avoid the costly time associated with constant calls to the Revit API. We can store the information we need once, and then efficiently query that data in a similar way to the familiar SQL database queries.
LINQ was introduced with the .NET Framework 3.5. The main benefit is an advanced native data query capability. For example, if we wanted to find all diffusers in a model that had a CFM of over 500, we could use a LINQ query to find them:
var results = from d in allDiffusers let x = 500 where d.CFM < x select d;
Although LINQ takes a little more time to set up initially, repeated queries will perform much faster. We had an instance where we wanted to place nearly 15,000 connectors in our model to correspond with an architectural model.
LINQ queries have a running time of O(n), but when used with Revit we find that we can greatly reduce our search time if we store information in an object that we will reuse. Our initial method kept a list of elements and then checked each element.Location for a match. This caused us to use the Revit API thousands of times unnecessarily. By storing the location in an object and then using LINQ to find a match, our process time went from over 14 hours down to under 10 minutes.
We created an object that stored the information about each lighting fixture in the architect's model and also the information we needed about our connectors. Through the use of a LINQ query, we were able to search to see if a connector existed at the same location as the architect's lighting fixture.
It became as simple as the following code to find out if a connector existed in the same location as the lighting fixture we were checking:
var found = from con in Connectors where con.ConLocation.Equals( location ) select con;
LINQ can work with a ton of different type of data sources. Some of the most popular are SQL, XML, ADO.NET datasets, .NET objects and several other third party sources which even include an ongoing project for the creation of LINQ to Google. In order to keep our code self contained and as simple as possible, we create a custom object to use with our LINQ queries.
To start with, we need to create a new class to store the information that we would like to query:
class InstanceData { #region Field Data private Element _instance; private String _param1; private bool _param2; private int _param3; #endregion #region Properties public Element Instance { get { return _instance; } set { _instance = value; } } public String Param1 { get { return _param1; } set { _param1 = value; } } public bool Param2 { get { return _param2; } set { _param2 = value; } } public int Param3 { get { return _param3; } set { _param3 = value; } } #endregion #region Constructors public InstanceData( Element instance ) { Instance = instance; foreach( Parameter param in Instance.Parameters ) { // Param1 returns string value if( param.Definition.Name == "Param1" ) { Param1 = param.AsString(); } // Param2 is a checkbox returning 0 or 1 if( param.Definition.Name == "Param2" ) { Param2 = (0 != param.AsInteger()); } // Param3 returns an integer value if( param.Definition.Name == "Param3" ) { Param3 = param.AsInteger(); } } } #endregion }
Now that we have our custom class created, we just need to populate it with the data we would like to query. This can be done many ways. For this example we will simply retrieve all the family instances in the model:
CreationFilter cf = app.Create.Filter; Filter f1 = cf.NewTypeFilter( typeof( FamilyInstance ) ); List<Element> a = new List<Element>(); doc.get_Elements( f1, a ); List<InstanceData> instanceDataList = new List<InstanceData>(); foreach( Element e in a ) { instanceDataList.Add( new InstanceData( e ) ); } string s = "value1"; bool b = true; int i = 42; var found = from instance in instanceDataList where (instance.Param1.Equals( s ) && b == instance.Param2 && i < instance.Param3) select instance; foreach( InstanceData instance in found ) { // Do whatever you would like }
So that was it from Joel, and back to Jeremy again. Thank you ever so much, Joel, for this fantastic introduction and example!
While preparing this article, I was a bit stumped to compile the sample code as part of The Building Coder samples until I noticed that I have to switch to the .NET Framework 3.5 before I can use Linq in C#. Watch out for this if you are trying to add Linq functionality to existing older projects.
Two things to note here, which make this information ever more globally usable:
- This is a very powerful optimisation technology in any environment, whether inside of Revit or elsewhere.
- Possibly the same kind of performance enhancement could be achieved by using some other caching mechanism independent of Linq. The main factor here is possibly the local storage of the data to be processed, avoiding repeated expensive calls to query it from Revit.
One extremely powerful advantage of Linq is its extremely readable query syntax.