While exploring Roslyn I put together a small app that should include a trace statement as the first statement in every method found in a Visual Studio Solution. My code is buggy and is only updating the first method.
The line that is not working as expected is flagged with a “TODO” comment. Please, advise.
I also welcome style recommendations that would create a more streamlined/readable solution.
Thanks in advance.
...
private void TraceBtn_Click(object sender, RoutedEventArgs e) {
var myWorkSpace = new MyWorkspace("...Visual Studio 2012\Projects\Tests.sln");
myWorkSpace.InjectTrace();
myWorkSpace.ApplyChanges();
}
...
using System;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
namespace InjectTrace
{
public class MyWorkspace
{
private string solutionFile;
public string SolutionFile {
get { return solutionFile; }
set {
if (string.IsNullOrEmpty(value)) throw new Exception("Invalid Solution File");
solutionFile = value;
}
}
private IWorkspace loadedWorkSpace;
public IWorkspace LoadedWorkSpace { get { return loadedWorkSpace; } }
public ISolution CurrentSolution { get; private set; }
public IProject CurrentProject { get; private set; }
public IDocument CurrentDocument { get; private set; }
public ISolution NewSolution { get; private set; }
public MyWorkspace(string solutionFile) {
this.SolutionFile = solutionFile;
this.loadedWorkSpace = Workspace.LoadSolution(SolutionFile);
}
public void InjectTrace()
{
int projectCtr = 0;
int documentsCtr = 0;
int transformedMembers = 0;
int transformedClasses = 0;
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var newDocRoot = docRoot;
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
IDocument newDocument = null;
//..for each Class in the Document..
foreach (var #class in classes) {
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods) {
//..insert a Trace Statement
var newMethod = InsertTrace(currMethod);
transformedMembers++;
//TODO: PROBLEM IS HERE
newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod);
}
if (transformedMembers != 0) {
newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
transformedMembers = 0;
transformedClasses++;
}
}
if (transformedClasses != 0) {
NewSolution = NewSolution.UpdateDocument(newDocument);
transformedClasses = 0;
}
documentsCtr++;
}
projectCtr++;
if (projectCtr > 2) return;
}
}
public MethodDeclarationSyntax InsertTrace(MethodDeclarationSyntax currMethod) {
var traceText =
#"System.Diagnostics.Trace.WriteLine(""Tracing: '" + currMethod.Ancestors().OfType<NamespaceDeclarationSyntax>().Single().Name + "." + currMethod.Identifier.ValueText + "'\");";
var traceStatement = Syntax.ParseStatement(traceText);
var bodyStatementsWithTrace = currMethod.Body.Statements.Insert(0, traceStatement);
var newBody = currMethod.Body.Update(Syntax.Token(SyntaxKind.OpenBraceToken), bodyStatementsWithTrace,
Syntax.Token(SyntaxKind.CloseBraceToken));
var newMethod = currMethod.ReplaceNode(currMethod.Body, newBody);
return newMethod;
}
public void ApplyChanges() {
LoadedWorkSpace.ApplyChanges(CurrentSolution, NewSolution);
}
}
}
The root problem of you code is that newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod); somehow rebuilds newDocRoot internal representation of code so next currMethod elements won't be find in it and next ReplaceNode calls will do nothing. It is a situation similar to modifying a collection within its foreach loop.
The solution is to gather all necessary changes and apply them at once with ReplaceNodes method. And this in fact naturally leads to simplification of code, because we do not need to trace all those counters. We simply store all needed transformation and apply them for whole document at once.
Working code after changes:
public void InjectTrace()
{
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
var dict = new Dictionary<CommonSyntaxNode, CommonSyntaxNode>();
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
//..for each Class in the Document..
foreach (var #class in classes)
{
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods)
{
//..insert a Trace Statement
dict.Add(currMethod, InsertTrace(currMethod));
}
}
if (dict.Any())
{
var newDocRoot = docRoot.ReplaceNodes(dict.Keys, (n1, n2) => dict[n1]);
var newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
NewSolution = NewSolution.UpdateDocument(newDocument);
}
}
}
}
Related
While using VisualStudio 2019 with my own source generator. I'm having an exception inside visual studio and it prompts me to open a debugger.
This only happens while coding and not while building (which make me thinks that my source generators is fine).
When trying to debug I see a blank page and call stack does not have any informations.
I know it must be linked to my generator but I don't know how. Any tips on how to debug would be greatly appreciated.
Full source code is available here : https://github.com/kYann/StrongTypeId/tree/master/src/StrongType.Generators
[Generator]
public class StrongTypeIdGenerator : ISourceGenerator
{
Template template;
public void Initialize(GeneratorInitializationContext context)
{
var file = "StrongTypeId.sbntxt";
template = Template.Parse(EmbeddedResource.GetContent(file), file);
context.RegisterForSyntaxNotifications(() => new StrongTypeIdReceiver());
}
private string GenerateStrongTypeId(string #namespace, string className)
{
var model = new
{
Namespace = #namespace,
ClassName = className,
};
// apply the template
var output = template.Render(model, member => member.Name);
return output;
}
public bool IsStrongTypeId(INamedTypeSymbol recordSymbol)
{
var strongTypeIdType = typeof(StrongTypeId<>);
var originalBaseTypeDef = recordSymbol.BaseType.OriginalDefinition;
var baseTypeAssembly = originalBaseTypeDef.ContainingAssembly;
var isSameAssembly = baseTypeAssembly.ToDisplayString() == strongTypeIdType.Assembly.FullName;
var isSameTypeName = strongTypeIdType.Name == originalBaseTypeDef.MetadataName;
return isSameAssembly && isSameTypeName;
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxReceiver is not StrongTypeIdReceiver receiver)
return;
foreach (var rds in receiver.RecordDeclarations)
{
var model = context.Compilation.GetSemanticModel(rds.SyntaxTree);
if (model.GetDeclaredSymbol(rds) is not INamedTypeSymbol recordSymbol)
continue;
if (!IsStrongTypeId(recordSymbol))
continue;
var ns = recordSymbol.ContainingNamespace.ToDisplayString();
var output = GenerateStrongTypeId(ns, recordSymbol.Name);
// add the file
context.AddSource($"{recordSymbol.Name}.generated.cs", SourceText.From(output, Encoding.UTF8));
}
}
private class StrongTypeIdReceiver : ISyntaxReceiver
{
public StrongTypeIdReceiver()
{
RecordDeclarations = new();
}
public List<RecordDeclarationSyntax> RecordDeclarations { get; private set; }
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is RecordDeclarationSyntax rds &&
rds.BaseList is not null)
{
this.RecordDeclarations.Add(rds);
}
}
}
}
RoslynCodeAnalysisService has an aggresive caching strategy and was holding previous source generator in memory.
Restarting visual studio did the tricks
Good day,
I try that when I connect two documents, the numbered lists are reset. As in the example below.
I use the Xceed.Words.NET DocX libary.
In this Sample, the Template is :
Test Header
List:
1) <-This should be one
And the Result is:
Test Header
List:
1) <-This should be one
Test Header
List:
2) <-This should be one
Test Header
List:
3) <-This should be one
With the following code I am able to create the lists with similar formatting, but the formatting does not match 100%. Does anyone have any idea which way to try otherwise?
Note the number of existing paragraphs and call the function to reset the list after inserting the document.
/// <summary>
/// Insert complete WordFile
/// </summary>
/// <param name="wordFile"></param>
public void InsertWordTemplate(IWordFile wordFile, bool InsertPageBreak)
{
if (wordFile != null && wordFile.IsFileOk)
{
int pargraphCount = Document.Paragraphs.Count - 1;
// NotNeeded for this Problem wordFile.RemoveStyles();
// NotNeeded for this Problem RemoveHeaderAndFooter(wordFile);
Document.InsertDocument(wordFile.Document);
// NotNeeded for this Problem ReplaceSectionBreak(InsertPageBreak, pargraphCount);
ResetNumberedList(pargraphCount);
logger.Info("Word file inserted: " + wordFile.File.FullName);
}
else
{
logger.Warn("Word file is not okay - will not be inserted: " + wordFile?.File?.FullName);
}
}
In the Word document, three different names are used in a list, only from the 4th level is worked with a level. For other Word templates they are called different.
private void ResetNumberedList(int pargraphCount)
{
string styleName1 = "ListNumbers";
string styleName2 = "PIgeordneteListe2Ebene";
string styleName3 = "PIgeordneteListe3Ebene";
NumberedListReset numberedListReset = new NumberedListReset(Document, styleName1, styleName2, styleName3);
bool OnlyFirstFoundList = true;
numberedListReset.Reset(pargraphCount, OnlyFirstFoundList);
}
Below is the helper class with which I try to reset the numbering. I do this by myself
I notice the formatting of the individual list items, create new lists, fill them with the old values, set the styles correctly again and then insert everything into old place.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Xceed.Document.NET;
using Xceed.Words.NET;
namespace PIB.Report.DataWriter.WordExport
{
public class NumberedListReset
{
private readonly DocX _Document;
private readonly string _StyleName1;
private readonly string _StyleName2;
private readonly string _StyleName3;
public NumberedListReset(DocX Document, string StyleName1, string StyleName2, string StyleName3)
{
_Document = Document;
_StyleName1 = StyleName1;
_StyleName2 = StyleName2;
_StyleName3 = StyleName3;
}
public void Reset(int StartParagraphNumber, bool OnlyFirstFinding)
{
for (int i = StartParagraphNumber; i < _Document.Paragraphs.Count; i++)
{
var paragraph = _Document.Paragraphs[i];
if (paragraph.IsListItem == true && paragraph.ListItemType == ListItemType.Numbered && paragraph.StyleName == _StyleName1)
{
//int? numId = GetNumId(paragraph);
//if (numId != -1)
//{
//}
ResetFoundList(ref i);
if (OnlyFirstFinding == true)
{
break;
}
}
}
}
private void ResetFoundList(ref int ParagraphCounter)
{
List<ParagraphMemorize> ParagraphMemorizes = CreateMemorizeListItems(ParagraphCounter);
if (ParagraphMemorizes.Count != 0)
{
RemoveOldParagraphsFromDocument(ParagraphMemorizes);
List numberedList = CreateNewDocumentList();
FillDocumentList(ParagraphMemorizes, numberedList);
List<Paragraph> actualListData = numberedList.Items;
ResetSyleNames(ParagraphMemorizes, actualListData);
InsertNewParagraphsToDocument(ParagraphCounter, actualListData);
ParagraphCounter += ParagraphMemorizes.Count;
}
}
private List<ParagraphMemorize> CreateMemorizeListItems(int ParagraphCounter)
{
List<ParagraphMemorize> ParagraphMemorizes = new List<ParagraphMemorize>();
for (int ii = ParagraphCounter; ii < _Document.Paragraphs.Count; ii++)
{
var paragraph = _Document.Paragraphs[ii];
if (!NameIsKnown(paragraph.StyleName))
{
break;
}
ParagraphMemorize paragraphMemorize = new ParagraphMemorize(paragraph);
paragraphMemorize.ListLevel = GetListLevel(paragraph);
ParagraphMemorizes.Add(paragraphMemorize);
}
return ParagraphMemorizes;
}
private void RemoveOldParagraphsFromDocument(List<ParagraphMemorize> ParagraphMemorizes)
{
ParagraphMemorizes.ForEach(m => _Document.RemoveParagraph(m.Paragraph));
}
private List CreateNewDocumentList()
{
return _Document.AddList(startNumber: 1);
}
private void FillDocumentList(List<ParagraphMemorize> ParagraphMemorizes, List numberedList)
{
for (var ii = 0; ii < ParagraphMemorizes.Count; ii++)
{
//numberedList.AddItem(ParagraphMemorizes[ii].Paragraph); //Raised an Error
ParagraphMemorize paragraphMemorize = ParagraphMemorizes[ii];
int listLevel = GetListLevel(paragraphMemorize);
_Document.AddListItem(numberedList, paragraphMemorize.Text, listLevel);
}
}
private static void ResetSyleNames(List<ParagraphMemorize> ParagraphMemorizes, List<Paragraph> actualListData)
{
for (int ii = 0; ii < actualListData.Count; ii++)
{
actualListData[ii].StyleName = ParagraphMemorizes[ii].StyleName;
}
}
private void InsertNewParagraphsToDocument(int i, List<Paragraph> actualListData)
{
Paragraph paragraph = _Document.Paragraphs[i];
for (int ii = 0; ii < actualListData.Count; ii++)
{
paragraph.InsertParagraphBeforeSelf(actualListData[ii]);
}
}
private bool NameIsKnown(string Name)
{
return Name == _StyleName1 | Name == _StyleName2 | Name == _StyleName3;
}
private int GetListLevel(ParagraphMemorize paragraphMemorize)
{
if (paragraphMemorize.StyleName == _StyleName1)
{
return 0;
}
else if (paragraphMemorize.StyleName == _StyleName2)
{
return 1;
}
else if (paragraphMemorize.StyleName == _StyleName3)
{
return (int)paragraphMemorize.ListLevel;
}
else
{
return 0;
}
}
private int? GetNumId(Paragraph paragraph)
{
var numIds = paragraph.ParagraphNumberProperties.Descendants().Where(e => e.Name.LocalName.Equals("numId"));
foreach (var numId in numIds)
{
XNamespace nsW = Namespace.WordNamespace;
var values = numId.Attributes(XName.Get("val", nsW.ToString()));
foreach (var value in values)
{
int resultId = 0;
int.TryParse(value.Value, out resultId);
return resultId;
}
}
return null;
}
private int? GetListLevel(Paragraph paragraph)
{
var numIds = paragraph.ParagraphNumberProperties.Descendants().Where(e => e.Name.LocalName.Equals("ilvl"));
foreach (var numId in numIds)
{
XNamespace nsW = Namespace.WordNamespace;
var values = numId.Attributes(XName.Get("val", nsW.ToString()));
foreach (var value in values)
{
int resultId = 0;
int.TryParse(value.Value, out resultId);
return resultId;
}
}
return null;
}
private class ParagraphMemorize
{
public Paragraph Paragraph { get; set; }
public string Text { get; set; }
public string StyleName { get; set; }
public int? ListLevel { get; set; }
public ParagraphMemorize(Paragraph Paragraph)
{
this.Paragraph = Paragraph;
this.Text = Paragraph.Text;
this.StyleName = Paragraph.StyleName;
}
}
}
}
What am I missing here? Is there an option that XMLDiff should care about element names and seek for best match to recognize following changes correctly?
a Helper class for making comparisons between two XML files:
public class XMLDiffer
{
public XDocument Diff(string originalXML, string changedXML)
{
//http://msdn2.microsoft.com/en-us/library/aa302294.aspx
XmlDiff xmlDiff = new XmlDiff(XmlDiffOptions.IgnoreChildOrder | XmlDiffOptions.IgnoreComments | XmlDiffOptions.IgnoreWhitespace);
xmlDiff.Algorithm = XmlDiffAlgorithm.Precise;
StringBuilder diffgramStringBuilder = new StringBuilder();
bool xmlComparisonResult = false;
using (StringReader legacySr = new StringReader(originalXML), nextgenSr = new StringReader(changedXML))
{
using (XmlReader legacyReader = XmlReader.Create(legacySr), nextgenReader = XmlReader.Create(nextgenSr))
{
using (StringWriter sw = new StringWriter(diffgramStringBuilder))
{
using (XmlWriter diffgramWriter = XmlWriter.Create(sw))
{
xmlComparisonResult = xmlDiff.Compare(legacyReader, nextgenReader, diffgramWriter);
}
}
}
}
XDocument xdoc = XDocument.Parse(diffgramStringBuilder.ToString());
return xdoc;
}
public string GetChangeHtml(string originalXML, string changedXML)
{
XmlDiffView view = new XmlDiffView();
var diffgram = Diff(originalXML, changedXML);
string ret = "";
using (StringReader legacySr = new StringReader(originalXML), diffGramSr = new StringReader(diffgram.ToString()))
{
using (XmlReader legacyReader = XmlReader.Create(legacySr), diffgramReader = XmlReader.Create(diffGramSr))
{
using (StringWriter sw = new StringWriter())
{
view.Load(legacyReader, diffgramReader);
view.GetHtml(sw);
ret = sw.ToString();
}
}
}
return ret;
}
}
With Following test:
[TestMethod]
public void XMLDiff_AreNotSame_GetChangeHtmlAll()
{
//Arrange
string source = "<root><child>some text</child><child>more text</child><child1>REMOVED</child1></root>";
//Ordering of the generic child nodes is not changed, but it might
string target = "<root><child>some text CHANGE</child><child>more text</child><child>ADDITION</child></root>";
XMLDiffer differ = new XMLDiffer();
//Act
var diffview = differ.GetChangeHtml(source, target);
//Assert
Assert.IsNotNull(diffview);
}
Produces following (html and table elements added):
https://pste.eu/p/Fm7Z.html
More info about library: http://msdn2.microsoft.com/en-us/library/aa302294.aspx
Nuget link for references: https://www.nuget.org/packages/XMLDiffPatch/
I ended up implementing following classes to get changes:
public class XMLComparer : IEqualityComparer<XNode>
{
public bool Equals(XNode e1, XNode e2)
{
if (!(e1 is XElement)) return true;
if (!(e2 is XElement)) return false;
var el1 = e1 as XElement;
var el2 = e2 as XElement;
return Tuple.Create(el1.Name, el1.Value).Equals(Tuple.Create(el2.Name, el2.Value));
}
public int GetHashCode(XNode n)
{
if (!(n is XElement)) return 0;
var el = n as XElement;
return Tuple.Create(el.Name, el.Value).GetHashCode();
}
}
public class XMLDifference
{
public bool IsNew { get; set; }
public XElement Node { get; set; }
}
public class XMLDifferenceComparer
{
public List<XMLDifference> GetDifferences(string original, string changed)
{
List<XMLDifference> ret = new List<XMLDifference>();
var originalDoc = XDocument.Parse(original);
var changedDoc = XDocument.Parse(changed);
//Get differences that are present in new xml version
var differences = changedDoc.Root.Descendants().Except(originalDoc.Root.Descendants(), new XMLComparer());
ret.AddRange(GetList(differences, true));
//Get differences that have changed since the old xml version
var oldValues = originalDoc.Root.Descendants().Except(changedDoc.Root.Descendants(), new XMLComparer());
ret.AddRange(GetList(oldValues, false));
return ret;
}
private List<XMLDifference> GetList(IEnumerable<XNode> nodes, bool isNew)
{
List<XMLDifference> ret = new List<XMLDifference>();
foreach (XNode d in nodes)
{
var diff = new XMLDifference();
diff.IsNew = isNew;
var el = d as XElement;
diff.Node = el;
ret.Add(diff);
}
return ret;
}
}
This can recognize changes but is not element specific, it cannot map which element exactly was changed and how, caused by lack of unique identifiers for each element.
The main idea for this solution came from here: https://gist.github.com/krcourville/6933451
Rookie here needing help. I'm trying to build a prototype with the neo4j .NET driver using Bolt. My aim with the prototype is building multiple methods for creation and searches in the db, but only one method to connect to the db - here I'm continuously having problems. I've Googled all weekend for examples, tutorials and traversed through the documentation and now I need your help.
Programs.cs
using System;
using DTUneo4jConsoleApp.Db;
namespace DTUneo4jConsoleApp
{
public class Program
{
public static void Main(string[] args)
{
MyProperties something = new MyProperties();
neo4jdb session = new neo4jdb();
session.Run($"CREATE (a:Person {{name:'{something.Name}', title:'{something.Title}'}})");
var result = session.Run($"MATCH (a:Person) WHERE a.name = '{something.Name}' RETURN a.name AS name, a.title AS title");
foreach (var record in result)
{
Console.WriteLine($"{record["title"].As<string>()} {record["name"].As<string>()}");
}
Console.ReadKey();
}
}
public class MyProperties
{
public string Name { get; set; }
public string Title { get; set; }
}
}
db.cs
using Neo4j.Driver.V1;
namespace DTUneo4jConsoleApp.Db
{
public class neo4jdb
{
public static void Connection()
{
using (var driver = GraphDatabase.Driver("bolt://localhost", AuthTokens.Basic("user", "pass")))
using (var session = driver.Session())
{
}
}
}
}
When I instantiate the neo4jdb session = new neo4jdb(); I don't get i.e. the Run() method from the driver.
I hope someone can guide me in the right direction.
I am doing it like this:
public static List<IStatementResult> ExecuteCypher(List<Statement> statements)
{
List<IStatementResult> results = new List<IStatementResult>();
using (var driver = GraphDatabase.Driver("bolt://localhost", AuthTokens.Basic("user", "pass")))
{
using (var session = driver.Session())
{
using (var tx = session.BeginTransaction())
{
foreach (var statement in statements)
{
results.Add(tx.Run(statement));
}
tx.Success();
}
}
}
return results;
}
usage:
MyProperties something = new MyProperties();
var createCypher = new Statement($"CREATE (a:Person {{name:'{something.Name}', title:'{something.Title}'}})");
var matchCypher = new Statement($"MATCH (a:Person) WHERE a.name = '{something.Name}' RETURN a.name AS name, a.title AS title");
var statements = new List<Statement>();
statements.Add(createCypher);
statements.Add(matchCypher);
var results = ExecuteCypher(statements);
//you can now query result for each statement or
//your query your desired result
foreach (var record in results.Last())
{
Console.WriteLine($"{record["title"].As<string>()} {record["name"].As<string>()}");
}
In this way I can also create multiple records in a single transaction and get the result of all those as well.
I have objects in Autocad drawing with property named Base. I am trying to find all objects in that drawing with Base property has a specific string value such as "Pipe".
I can iterate objects in the drawing and get all object ids. Then I get all properties of object with that Id and check if property named Base = "Pipe".
Iteration performance is not good enough. Is there any way to directly get object ids that has property named Base = "Pipe"?
Here is how I iterate through all objects:
List<ObjectId> ObjectIds = new List<ObjectId>();
foreach (Document Document in Documents)
{
Database Database = Document.Database;
using (Transaction Transaction = Database.TransactionManager.StartTransaction())
{
for (long i = Database.BlockTableId.Handle.Value; i < Database.Handseed.Value; i++)
{
ObjectId Id;
if (Database.TryGetObjectId(new Handle(i), out Id))
{
ObjectIds.Add(Id);
}
}
Transaction.Commit();
}
}
And here is how I get all properties of the objects in my ObjectIds collection.
public static DataLinksManager DataLinks
{
get
{
if (null == _DataLinks)
{
StringCollection Coll = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetLinkManagerNames();
if (Coll.Count > 0)
{
if (Coll[0] != string.Empty)
{
_DataLinks = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetManager(Coll[0]);
}
}
}
return _DataLinks;
}
}
private static DataLinksManager _DataLinks;
foreach(var Id in ObjectIds)
{
List<KeyValuePair<string, string>> Properties = DataLinks.GetAllProperties(Id, true);
// I check existence of my property and if so its value.
}
In case anyone needs, here is the code that is solution to my problem. The trick is the Iterate method. This is based on this article by Philippe Leefsma. What I add to this method is a list of ObjectClass properties of found ObjectId instances. My sample dawing has around 8500 ObjectIds. However what I'm interested in is objects with base classes acppasset and acppdynamicasset and count of such objects is 90.
using Autodesk.AutoCAD.ApplicationServices;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
public static void GetObjects()
{
List<KeyValuePair<string, ObjectId>> ObjectIds = new List<KeyValuePair<string, ObjectId>>();
List<string> Filter = new List<string>() { "acppasset", "acppdynamicasset" };
foreach (Document Document in this.Documents)
{
ObjectIdCollection Ids = this.Iterate(Document, Filter);
if (null != Ids) foreach (var Id in Ids.OfType<ObjectId>()) ObjectIds.Add(new KeyValuePair<string, ObjectId>(System.IO.Path.GetFileNameWithoutExtension(Document.Name), Id));
}
this.Results = new Dictionary<string, List<List<KeyValuePair<string, string>>>>();
foreach (var Id in ObjectIds)
{
try
{
var Properties = this.GetObject(Id.Value);
if (null == Properties) continue;
var Base = Properties.Where(x => x.Key == "Base").FirstOrDefault();
if (string.IsNullOrWhiteSpace(Base.Value)) continue;
if (!this.Results.ContainsKey(Base.Value)) this.Results.Add(Base.Value, new List<List<KeyValuePair<string, string>>>());
this.Results[Base.Value].Add(Properties);
} catch { }
}
}
public ObservableCollection<Document> Documents { get; set; }
public ObjectIdCollection Iterate(Document Document, List<string> Filter = null)
{
ads_name Instance = new ads_name();
Database Database = Document.Database;
ObjectIdCollection ValidIds = new ObjectIdCollection();
// Get the last handle in the Database
Handle Handseed = Database.Handseed;
// Copy the handseed total into an efficient raw datatype
long HandseedTotal = Handseed.Value;
for (long i = 1; i < HandseedTotal; ++i)
{
string Handle = Convert.ToString(i, 16);
int Result = acdbHandEnt(Handle, ref Instance);
if (Result != 5100) continue; // RTNORM
ObjectId Id = new ObjectId(Instance.a);
if (!Id.IsValid) continue;
try
{
if (null != Filter)
{
if (!Filter.Contains(Id.ObjectClass.Name.ToLower())) continue;
}
using (DBObject DBObject = Id.Open(OpenMode.ForRead, false))
{
ValidIds.Add(Id);
DBObject.Dispose();
}
} catch { }
}
return ValidIds;
}
public List<KeyValuePair<string, string>> GetObject(ObjectId Id)
{
if (Command.DataLinks != null) try { return Command.DataLinks.GetAllProperties(Id, true); } catch { return null; }
return null;
}
public static DataLinksManager DataLinks
{
get
{
if (null == _DataLinks)
{
StringCollection Coll = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetLinkManagerNames();
if (Coll.Count > 0)
{
if (Coll[0] != string.Empty)
{
_DataLinks = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetManager(Coll[0]);
}
}
}
return _DataLinks;
}
}
private static DataLinksManager _DataLinks;
public Dictionary<string, List<List<KeyValuePair<string, string>>>> Results { get; set; }
The slow performance here is because it attempts to read all the objects and check if it contains any attribute. As far as I know, the attributes exist only for block references(inserts). So if selection filters are used, we could get direct access to only those records based on the filter criteria.
I found a pretty easy example here using selection filter that selects all blocks with a particular name.
Copying a part of that code for reference. This selects only the block references. You can iterate from here.
TypedValue[] filterlist = new TypedValue[1];
filterlist[0] = new TypedValue(0, "INSERT");
SelectionFilter filter = new SelectionFilter(filterlist);
PromptSelectionResult selRes = ed.SelectAll(filter);
if (selRes.Value.Count != 0)
{
SelectionSet set = selRes.Value;
foreach (ObjectId id in set.GetObjectIds())
{
BlockReference oEnt = (BlockReference)tr.GetObject(id, OpenMode.ForWrite);
//do something with oEnt..;
}
}
If you can add complexities to your filter, you will need to iterate only a very small set.