Author Topic: AutoCAD & Dynamic LINQ  (Read 3823 times)

0 Members and 1 Guest are viewing this topic.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
AutoCAD & Dynamic LINQ
« on: October 15, 2012, 03:26:41 PM »
I have wrote such simple code:
Code - C#: [Select]
  1. // Dynamic LINQ:
  2. // http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
  3.  
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Linq.Dynamic;
  8. using System.Text;
  9. //Autodesk namespaces ***************
  10. using acad = Autodesk.AutoCAD.ApplicationServices.Application;
  11. using AcApp = Autodesk.AutoCAD.ApplicationServices;
  12. using AcDb = Autodesk.AutoCAD.DatabaseServices;
  13. using AcEd = Autodesk.AutoCAD.EditorInput;
  14. using AcRtm = Autodesk.AutoCAD.Runtime;
  15. using AcPub = Autodesk.AutoCAD.Publishing;
  16. using AcPlt = Autodesk.AutoCAD.PlottingServices;
  17. using AcInt = Autodesk.AutoCAD.Internal;
  18. using AcGem = Autodesk.AutoCAD.Geometry;
  19. using AcCol = Autodesk.AutoCAD.Colors;
  20. using AcCMod = Autodesk.AutoCAD.ComponentModel;
  21. using AcGInt = Autodesk.AutoCAD.GraphicsInterface;
  22. using AcGSys = Autodesk.AutoCAD.GraphicsSystem;
  23. using AcLayMng = Autodesk.AutoCAD.LayerManager;
  24. using AcWin = Autodesk.AutoCAD.Windows;
  25. //************************************
  26.  
  27. namespace AcDbDynamicLinq {
  28.     public class Class1 {
  29.         [AcRtm.CommandMethod("test", AcRtm.CommandFlags.Modal)]
  30.         public void Test() {
  31.             AcApp.Document doc = acad.DocumentManager.MdiActiveDocument;
  32.             AcEd.Editor ed = doc.Editor;
  33.             AcDb.Database db = doc.Database;
  34.  
  35.             // Full ids list
  36.             List<AcDb.ObjectId> ids = new List<AcDb.ObjectId>();
  37.  
  38.             // Fill the ids list...
  39.             for (Int64 i = db.BlockTableId.Handle.Value; i < db.Handseed.Value; i++) {
  40.                 AcDb.ObjectId item = AcDb.ObjectId.Null;
  41.                 AcDb.Handle handle = new AcDb.Handle(i);
  42.                 // Try to get ObjectId, and check it...
  43.                 if (db.TryGetObjectId(handle, out item) && item.IsValid && !item.IsErased && !item.IsNull)
  44.                     ids.Add(item);
  45.             }
  46.             AcEd.PromptStringOptions pso = new AcEd.PromptStringOptions("Write LINQ expression for getting necessary ids: ");
  47.             pso.AllowSpaces = true;
  48.             AcEd.PromptResult expression = ed.GetString(pso);
  49.             if (expression.Status != AcEd.PromptStatus.OK) {
  50.                 ed.WriteMessage("\nCommand canceled.\n");
  51.                 return;
  52.             }
  53.  
  54.             AcDb.ObjectId[] result = null;
  55.             using (AcDb.Transaction tr = db.TransactionManager.StartTransaction()) {
  56.                 try {
  57.                     // EXAMPLES OF QUERY STRING:
  58.                     // Example 1: it.ObjectClass.Name.ToUpper().Contains("TEXTSTYLE")  
  59.                     // Example 2: @0.GetObject(it, @1) != null
  60.                     // Example 3: it.GetObject(@1) != null
  61.                     result = ids.AsQueryable().Where(expression.StringResult, tr, AcDb.OpenMode.ForRead).ToArray();
  62.                 }
  63.                 catch (Exception ex) {
  64.                     ed.WriteMessage("\nException: {0}.\n", ex.Message);
  65.                     return;
  66.                 }
  67.                 tr.Commit();
  68.             }
  69.  
  70.             ed.WriteMessage("\nWas filtered {0} items.\n", result.Length);
  71.             foreach (AcDb.ObjectId id in result) {
  72.                 ed.WriteMessage("\n{0}", id.ObjectClass.Name);
  73.             }
  74.             ed.WriteMessage("\n");
  75.         }
  76.     }
  77. }
  78.  
Read the comments on my code. The "Example 1" works fine, but other variants I can't use. Anybody help me to solve it problem?

Best Regards,
Andrey

kaefer

  • Guest
Re: AutoCAD & Dynamic LINQ
« Reply #1 on: October 15, 2012, 06:33:26 PM »
The "Example 1" works fine,

Hi Andrey,

no, it doesn't. It operates on IEnumerable instead. What do you want to achieve?

By any chance, do you mean something along those lines?
Code - C#: [Select]
  1.                     ConstantExpression xTrans = Expression.Constant(tr);
  2.                     ParameterExpression xObjectId = Expression.Parameter(typeof(AcDb.ObjectId));
  3.                     ConstantExpression xOpenMode = Expression.Constant(AcDb.OpenMode.ForRead);
  4.                     ConstantExpression xFalse = Expression.Constant(false);
  5.                     ConstantExpression xNull = Expression.Constant(null);
  6.                     Type[] typeArgs = new[] { typeof(AcDb.ObjectId), typeof(AcDb.OpenMode), typeof(Boolean), typeof(Boolean) };
  7.                     Expression[] parameters = new Expression[] { xObjectId, xOpenMode, xFalse, xFalse };
  8.                     MethodInfo method = typeof(AcDb.Transaction).GetMethod("GetObject", typeArgs);
  9.                     MethodCallExpression call = Expression.Call(xTrans, method, parameters);
  10.                     BinaryExpression notEq = Expression.NotEqual(call, xNull);
  11.                     Expression<Func<AcDb.ObjectId, bool>> ex = Expression.Lambda<Func<AcDb.ObjectId, bool>>(notEq, xObjectId);
  12.                     result = ids.AsQueryable().Where(ex).ToArray();
  13.  

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: AutoCAD & Dynamic LINQ
« Reply #2 on: October 16, 2012, 01:06:57 AM »
no, it doesn't.
Did you try? Did you add Dynamic.cs into your project, and add
Code - C#: [Select]
  1. using System.Linq.Dynamic;
code? It is works for me.
no, it doesn't. It operates on IEnumerable instead. What do you want to achieve?
By any chance, do you mean something along those lines?
At my code I get full array of ObjectId items. Then I want to allow for advanced users to create flexible filters for selection necessary ids.

I would like to create a textbox, into which user must to write a filter string. For example, next query must to allow me get all ids for dimensions with invalid dimension text:

Quote
var x = @0.GetObject(it, @1) as Autodesk.AutoCAD.DatabaseServices.Dimension && x.DimensionText != "<>"

This string expression I want, for example, to apply for each item on 'ids'. The using of such string expressions will allow for advanced users to create flexible filters for selection necessary ids.

TheMaster

  • Guest
Re: AutoCAD & Dynamic LINQ
« Reply #3 on: October 16, 2012, 05:07:58 AM »
The "Example 1" works fine,

Hi Andrey,

no, it doesn't. It operates on IEnumerable instead. What do you want to achieve?


Yes it does (the first example, that is).

His second and third examples will not work because the query language implemented by DynamicExpression only supports method invocation on a limited set of types (which can be remedied by adding the types that he wants to invoke methods on to the internal list of supported types).

I came across DynamicExpression a few years back when I was researching the feasibility of writing a Linq-to-SelectionFilter Query provider (which I started but never finished  :oops: ), and played with it briefly, and while it does have some use in AutoCAD development, the basic problem with it is that it doesn't support casting to derived types, so you can't write a simple query that filters based on object type, and also on properties of more specific types without casting.

This requires the attached AcDbLinq.cs

Code - C#: [Select]
  1. using System.Linq;
  2. using System.Linq.Dynamic;
  3. using Autodesk.AutoCAD.ApplicationServices;
  4. using Autodesk.AutoCAD.DatabaseServices;
  5. using Autodesk.AutoCAD.Runtime;
  6.  
  7. namespace DynamicExpression.Example
  8. {
  9.    public static class DynamicExpressionTest
  10.    {
  11.  
  12.       /// <summary>
  13.       /// This example requires the DynamicExpression API referenced at:
  14.       /// http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
  15.       ///
  16.       /// The DXTEST command excercises rudimentary use of DynamicExpression to
  17.       /// generate a query expression from a string. In this example, the string
  18.       /// is hard-coded, but might just as easily be derived from user input, etc.,
  19.       /// at runtime in real world use. In essence, DynamicExpression implements
  20.       /// a very basic, rudimentary query language that has a syntax designed to
  21.       /// be familiar to C#, VB and SQL users.
  22.       ///
  23.       /// It should be noted however, that direct use of user-supplied query
  24.       /// strings can be dangerous, without extensive error checking.
  25.       ///
  26.       /// This example will obtain all insertions of the block named 'DOOR'
  27.       /// that are inserted on the layer named 'LEVEL1', and change their
  28.       /// color to red. Note that the dynamic expression is not used to
  29.       /// filter entities by type, as this is done using ObjectIds rather
  30.       /// than opened DBObjects. Hence, the input sequence contains only
  31.       /// BlockReference objects and is the type which the query operates
  32.       /// on and whose properties are used in the query string.
  33.       ///
  34.       /// The example code also requires the AutoCAD Linq Extension Library
  35.       /// which is included (AcDbLinq.cs).
  36.       ///
  37.       /// </summary>
  38.  
  39.       [CommandMethod( "DXTEST" )]
  40.       public static void Method1()
  41.       {
  42.          Document doc = Application.DocumentManager.MdiActiveDocument;
  43.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  44.          {
  45.  
  46.             /// This returns an object that iterates over all BlockRefence
  47.             /// objects in model space:
  48.  
  49.             var blockrefs = doc.Database.GetModelSpace<BlockReference>().AsQueryable();
  50.  
  51.             /// There are three basic ways to compose a query string, and which is
  52.             /// best depends on where the parameters are coming from, and where the
  53.             /// query string itself comes from. For example, only the parameters
  54.             /// may be supplied by the user, or the entire query string may be
  55.             /// supplied by the user.
  56.             ///
  57.             /// The first way is simply to express it as a literal string, using
  58.             /// escaped double-quotes to surround string constants, thusly:
  59.            
  60.             /// var query = blockrefs.Where( "Name == \"DOOR\" && Layer = \"LEVEL1\"" );
  61.             ///
  62.             /// Another way of doing it is by constructing the entire
  63.             /// query string using String.Format():
  64.             ///
  65.             /// string queryString = string.Format(
  66.             ///      "Name == \"{0}\" AND Layer == \"{1}\"", "DOOR", "LEVEL1" );
  67.             /// var query = blockrefs.Where( queryString );
  68.             ///
  69.             /// And lastly, and the one we actually use here, is by using replacements,
  70.             /// which has the advantage of not requiring string parameters to be
  71.             /// surrounded by double quotes, presumably because the type of a parameter
  72.             /// can be inferred by the type of the expression on the left side of the
  73.             /// relational operator:
  74.            
  75.             var query = blockrefs.Where( "Name == @0 && Layer == @1", "DOOR", "LEVEL1" );
  76.  
  77.             /// The above 'query' var is an IQueryable<BlockReference>, which implicitly
  78.             /// is an IEnumerable<BlockReference> that we can walk using foreach():
  79.            
  80.             foreach( BlockReference door in query.UpgradeOpen() )
  81.             {
  82.                door.ColorIndex = 1;
  83.             }
  84.  
  85.             tr.Commit();
  86.          }
  87.       }
  88.  
  89.       /// <summary>
  90.       ///
  91.       /// A variation on the above that uses pattern matching for the
  92.       /// layer name, and selects all insertions of 'DOOR' from model
  93.       /// space, that reside on any layer whose name starts with "LEVEL".
  94.       /// Matching is achieved using the string.StartsWith() method
  95.       /// within the query expression.
  96.       ///
  97.       /// </summary>
  98.    
  99.       [CommandMethod( "DXTEST2" )]
  100.       public static void Method2()
  101.       {
  102.          Document doc = Application.DocumentManager.MdiActiveDocument;
  103.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  104.          {
  105.             var blockrefs = doc.Database.GetModelSpace<BlockReference>().AsQueryable();
  106.             var query = blockrefs.Where( "Name == @0 && Layer.StartsWith(@1)", "DOOR", "LEVEL" );
  107.             foreach( BlockReference door in query.UpgradeOpen() )
  108.             {
  109.                door.ColorIndex = 1;
  110.             }
  111.             tr.Commit();
  112.          }
  113.       }
  114.  
  115.       /// <summary>
  116.       /// This is a variation of the above example that uses DynamicExpression
  117.       /// to filter the initial set of ObjectIds and the opened BlockReferences.
  118.       /// </summary>
  119.  
  120.       [CommandMethod( "DXTEST3" )]
  121.       public static void Method3()
  122.       {
  123.          Document doc = Application.DocumentManager.MdiActiveDocument;
  124.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  125.          {
  126.             var ids = doc.Database.GetModelSpaceIds().AsQueryable();
  127.             var blockrefIds = ids.Where( "ObjectClass.DxfName == @0", "INSERT" );
  128.             var query = blockrefIds.GetObjects<BlockReference>().AsQueryable()
  129.                .Where( "Name == @0 && Layer.StartsWith(@1)", "DOOR", "LEVEL" );
  130.             foreach( BlockReference door in query.UpgradeOpen() )
  131.             {
  132.                door.ColorIndex = 1;
  133.             }
  134.             tr.Commit();
  135.          }
  136.       }
  137.    }
  138. }
  139.  
« Last Edit: October 16, 2012, 03:22:54 PM by ADNGhostWriter »

kaefer

  • Guest
Re: AutoCAD & Dynamic LINQ
« Reply #4 on: October 16, 2012, 05:53:25 AM »
no, it doesn't. It operates on IEnumerable instead. What do you want to achieve?


Yes it does (the first example, that is).

Wow. I stand corrected. We do need a query for this kind of wizardry.

Is this the source for System.Linq.Dynamic?

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: AutoCAD & Dynamic LINQ
« Reply #5 on: October 16, 2012, 06:28:07 AM »
2 ADNGhostWriter
I have some thoughts about Dynamic LINQ...
For example, I can to write my Check Standard Module, and all logic for selection, changing, and e.t.c. save as dynamic linq string expressions into XML file. If i will change XML contents, then my module will change the logic of its working, without recompiling.

TheMaster

  • Guest
Re: AutoCAD & Dynamic LINQ
« Reply #6 on: October 16, 2012, 06:29:23 AM »
no, it doesn't. It operates on IEnumerable instead. What do you want to achieve?


Yes it does (the first example, that is).

Wow. I stand corrected. We do need a query for this kind of wizardry.

Is this the source for System.Linq.Dynamic?

That's one source, but not the original one. It's included in a much larger library of samples provided by Microsoft, that you can get here:

     http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx


TheMaster

  • Guest
Re: AutoCAD & Dynamic LINQ
« Reply #7 on: October 16, 2012, 06:32:13 AM »
2 ADNGhostWriter
I have some thoughts about Dynamic LINQ...
For example, I can to write my Check Standard Module, and all logic for selection, changing, and e.t.c. save as dynamic linq string expressions into XML file. If i will change XML contents, then my module will change the logic of its working, without recompiling.

I suppose you can do that. You could also persist them in the drawing file itself, and in that case, a query could reference other objects in the same drawing, and their properties as well (that will take some work but its certainly doable).