Как обработать выражение LINQ, которое завершается ошибкой, если элемент отсутствует - PullRequest
1 голос
/ 26 августа 2011

Я новичок с LINQ to XML, и у меня есть этот код, который работает (большую часть времени):

private long processFile(StreamWriter oWriter, string inFileName)
    {
        XDocument xmlDoc = XDocument.Load(inFileName);
        List<DocMetaData> docList =
            (from d in xmlDoc.Descendants("DOCUMENT")
             select new DocMetaData
             {
                 Folder = d.Element("FOLDER").Attribute("name").Value
                 ,
                 File = d.Element("FILE").Attribute("filename").Value
                 ,
                 Comment = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Comment(idmComment)")
                    .First()
                    .Attribute("value").Value
                 ,
                 Title = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Title(idmName)")
                    .First()
                    .Attribute("value").Value
                 ,
                 DocClass = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Document Class(idmDocType)")
                    .First()
                    .Attribute("value").Value
             }
            ).ToList<DocMetaData>();
        OutputListToFile(oWriter, docList);
        return docList.LongCount();
    }

Ошибка в строке 117 (выражение выбора) с:

    System.NullReferenceException: Object reference not set to an instance of an object.
   at CBMI.WinFormsUI.GridForm.<processFile>b__3(XElement d) in C:\ProjectsVS2010\CBMI.LatitudePostConverter\CBMI.LatitudePostConverter\CBMI.WinFormsUI\GridForm.cs:line 117
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at CBMI.WinFormsUI.GridForm.processFile(StreamWriter oWriter, String inFileName) in C:\ProjectsVS2010\CBMI.LatitudePostConverter\CBMI.LatitudePostConverter\CBMI.WinFormsUI\GridForm.cs:line 115
   at CBMI.WinFormsUI.GridForm.btnProcess_Click(Object sender, EventArgs e) in C:\ProjectsVS2010\CBMI.LatitudePostConverter\CBMI.LatitudePostConverter\CBMI.WinFormsUI\GridForm.cs:line 85

Данные представляют собой правильно сформированный XML. В данном XML-файле много узлов <DOCUMENT>, и большинство, но не все, узлов <DOCUMENT> содержат узел <FOLDER>. Я обнаружил это с помощью грубой силы, открыв XML-файл с помощью VStudio 2010 и используя команду Find, которая показывает количество совпадающих строк.

Есть ли способ улучшить LINQ так, чтобы он не отказывал, когда данные не идеальны? И есть ли способ увидеть, какая часть выражения LINQ действительно не работает (я предполагаю, что это происходит из-за отсутствующих <FOLDER> узлов, но это может быть неправильно и является уродливым методом устранения неполадок).

Вот один <DOCUMENT>, который содержит соответствующий <FOLDER> узел (в самом низу):

    <?xml version="1.0" ?>
<DOCUMENTCOLLECTION>
<DOCUMENT>
<FILE filename="P:\LatitudeConsulting\LatConConverter-1.8.2\ConverterOutput\B0000002\3rd Party CON\D003694452.0001.tif" 
      outputpath="P:\LatitudeConsulting\LatConConverter-1.8.2\ConverterOutput\B0000002\3rd Party CON"/>
<ANNOTATION filename=""/>
<INDEX name="Access Level(idmAccessLevel)" value="Admin"/>
<INDEX name="Added By Group(idmAddedByGroup)" value="General Users"/>
<INDEX name="Added By User(idmDocOwner)" value="Import"/>
<INDEX name="Allow Secondary Version Lines?(idmDocVariants)" value="Yes"/>
<INDEX name="Application(idmVerApplication)" value=""/>
<INDEX name="Archive Category(idmDocDispCategory)" value="Archive"/>
<INDEX name="Archive Date(idmVerDispDate)" value=""/>
<INDEX name="Archive Repository(idmVerDispId)" value=""/>
<INDEX name="ArchivedDocument" value="NO"/>
<INDEX name="Availability Status(idmVerAvailStat)" value="Online"/>
<INDEX name="CAN(idmDocCustom4)" value=""/>
<INDEX name="Checked In By Group(idmVerCheckinGroup)" value="General Users"/>
<INDEX name="Checked In By User(idmVerCheckinUser)" value="Import"/>
<INDEX name="Checked Out?(idmVerCheckoutPending)" value="No"/>
<INDEX name="Checkin Date(idmVerCreateDate)" value="3/9/2001 9:20:38 AM"/>
<INDEX name="Child Count(idmVerCD)" value="0"/>
<INDEX name="Comment(idmComment)" value="1983\06_June_Meeting"/>
<INDEX name="Comment(idmVerComment)" value=""/>
<INDEX name="Content Search Repository(idmVerCsiId)" value=""/>
<INDEX name="Current Content Srch Repository(idmDocCurVerCsiId)" value=""/>
<INDEX name="Current Version Author(idmAddedByUser)" value="Import"/>
<INDEX name="Current Version Checked Out?(idmDocCurVerCheckedOut)" value="No"/>
<INDEX name="Current Version Date(idmDocCurVerDate)" value="3/9/2001 9:20:38 AM"/>
<INDEX name="Current Version ID(idmDocCurVerNum)" value="1"/>
<INDEX name="Current Version Index ID(idmDocCurVerCsiCid)" value=""/>
<INDEX name="Date Added(idmDateAdded)" value="3/9/2001 9:20:37 AM"/>
<INDEX name="Default Index Versions?(idmDocCsiDefault)" value="No"/>
<INDEX name="DiagnosticID(idmDocCustom5)" value="2-16.MDB-00015"/>
<INDEX name="Document Class(idmDocType)" value="3rd Party CON"/>
<INDEX name="Encrypted File Name(idmVerShelfFileId)" value="_276no__.__1"/>
<INDEX name="ExternalDocument" value="NO"/>
<INDEX name="File Name" value="51099.TIF"/>
<INDEX name="File Name(idmVerFileName)" value="51099.TIF"/>
<INDEX name="File Size(idmVerFileSize)" value="1166770"/>
<INDEX name="Has Annotations?(idmAnnotation)" value=""/>
<INDEX name="Index ID(idmVerCsiCid)" value=""/>
<INDEX name="Indexed Version Limit(idmDocCsiLimit)" value="1"/>
<INDEX name="Indexing Status(idmVerCsiStatus)" value="Not Indexed"/>
<INDEX name="Item ID(idmId)" value="003694452"/>
<INDEX name="Item ID(idmVerDocId)" value="003694452"/>
<INDEX name="Keyword(idmDocKeywords)" value=""/>
<INDEX name="Last Access Date(idmDateAccessed)" value="11/28/2003 3:05:30 PM"/>
<INDEX name="Last Access Date(idmDateModified)" value="8/24/2011 5:52:34 PM"/>
<INDEX name="Last Access Group(idmVerLastGroup)" value="Administrators"/>
<INDEX name="Last Access User(idmModifiedByUser)" value="Admin"/>
<INDEX name="Last Accessed Version(idmDocLastVerId)" value="1"/>
<INDEX name="Latest Version?(idmVerBranchCurVer)" value="Yes"/>
<INDEX name="Merge-Destination Version ID(idmVerMergeDst)" value="0"/>
<INDEX name="Merge-Source Version ID(idmVerMergeSrc)" value="0"/>
<INDEX name="MimeType" value="image/tiff"/>
<INDEX name="Min Item Delete Access Level(idmDocDeleteAccess)" value=""/>
<INDEX name="Modification Date(idmVerFileDate)" value="12/19/2000 11:12:30 AM"/>
<INDEX name="Number of Indexed Versions(idmDocCsiCount)" value="0"/>
<INDEX name="Offline Location(idmVerOfflineLocation)" value=""/>
<INDEX name="Online Disk Space(idmDocOnlineSize)" value="1166770"/>
<INDEX name="Online Limit(idmDocOnlineLimit)" value="5"/>
<INDEX name="Online Version Count(idmDocOnlineCount)" value="1"/>
<INDEX name="Origin ID(idmDocOriginID)" value=""/>
<INDEX name="Origin Library(idmDocOriginLibrary)" value=""/>
<INDEX name="Original File Name(idmDocOriginalFile)" value="51099.TIF"/>
<INDEX name="Permanent Index?(idmVerCsiPermanent)" value="No"/>
<INDEX name="Permanent Version?(idmVerPermanent)" value="No"/>
<INDEX name="Property ID(idmDocDynPropertyId)" value=""/>
<INDEX name="Protected?(idmDocProtected)" value="Yes"/>
<INDEX name="Publishing Status(idmPublish)" value=""/>
<INDEX name="Reclaim Pending?(idmVerReclaimPending)" value=""/>
<INDEX name="Reclaim Submitted Date(idmVerReclaimDate)" value=""/>
<INDEX name="Replica?(idmDocIsReplica)" value="No"/>
<INDEX name="ReplicatedDocument" value="NO"/>
<INDEX name="Secondary Version Line Count(idmVerBranchCount)" value="0"/>
<INDEX name="Source Version Checkout Date(idmVerPrevCheckoutDate)" value=""/>
<INDEX name="Storage Category(idmDocFileCategory)" value="Documents"/>
<INDEX name="Storage Repository(idmVerShelfId)" value="2"/>
<INDEX name="Title(idmName)" value="3rd Party CON Comments"/>
<INDEX name="Version ID(idmVerId)" value="1"/>
<FOLDER name="/NACAIE/1983/06_June_Meeting/NAPNSC"/>
</DOCUMENT>

РЕДАКТИРОВАТЬ: решение следует (содержит LINQ, который исправил эту проблему, когда узел FOLDER мог отсутствовать; использование First () могло быть опасной практикой, как отмечают другие, но в этом случае отсутствующие узлы FOLDER должны были обрабатываться):

namespace CBMI.Common
{
    public static class Extensions
    {
    public static string SafeGetAttributeValue(this XElement element, string attribute)
    {
        return (element != null) ?
          (element.Attribute(attribute) != null) ? 
              element.Attribute(attribute).Value : null : null;
    }
}
}
private long processFile(StreamWriter oWriter, string inFileName)
    {
        XDocument xmlDoc = XDocument.Load(inFileName);
        List<DocMetaData> docList =
            (from d in xmlDoc.Descendants("DOCUMENT")
             select new DocMetaData
             {
                 File = d.Element("FILE").Attribute("filename").Value
                 ,
                 ItemID = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Item ID(idmId)")
                    .First()
                    .Attribute("value").Value
                 ,
                 Comment = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Comment(idmComment)")
                    .First()
                    .Attribute("value").Value
                 ,
                 Title = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Title(idmName)")
                    .First()
                    .Attribute("value").Value
                 ,
                 DocClass = d.Elements("INDEX")
                    .Where(i => i.Attribute("name").Value == "Document Class(idmDocType)")
                    .First()
                    .Attribute("value").Value
                 ,
                 Folder = d.Element("FOLDER").SafeGetAttributeValue("name")
             }
            ).ToList<DocMetaData>();
        OutputListToFile(oWriter, docList);
        return docList.LongCount();
    }

Ответы [ 4 ]

5 голосов
/ 26 августа 2011

Вы всегда можете проверить данный узел, прежде чем пытаться выбрать его:

Folder = (d.Element("FOLDER") != null) ? (d.Element("FOLDER").Attribute("name") != null)
                                          ? Attribute("name").Value : null
                                       : null

Но я допускаю, что это может показаться некрасивым.В этом случае вы можете создать метод расширения XElement, который делает это:

public static class Extensions
{
   public static string SafeGetAttributeValue(this XElement element, string attribute)
   {
      return (element != null) ? (element.Attribute(attribute) != null)
                                              ? Attribute(attribute).Value : null
                                           : null
   }
}

, который вы можете использовать как:

select new DocMetaData
{
   Folder = d.Element("FOLDER").SafeGetAttributeValue("name"),
   //the rest of your object creation
}
1 голос
/ 26 августа 2011

Я не могу проверить, является ли это для вас проблемой, но First() выдает исключение, если в запросе нет значений. Вы можете избежать этого, используя вместо этого FirstOrDefault(), а затем проверяя, равен ли результат null.

0 голосов
/ 26 августа 2011

В вашем запросе linq для всех полей, которые могут быть нулевыми, и, следовательно, выдается исключение, если вы. Значите их, вы можете:

MyDone = PossibleNullNode != null ? PossibleNullNode.Value : WhateverFloatsYourBoat
0 голосов
/ 26 августа 2011

Мое эмпирическое правило заключается в том, чтобы никогда использовать методы First(), FirstOrDefault(), Single() или SingleOrDefault() в середине запроса. Это всегда должен быть последний метод, который вызывается. Если вам нужно выбрать свойство из выбранного элемента, сначала спроектируйте, а затем вызовите метод. Это приведет к тому, что код станет более понятным, и с такими ситуациями будет гораздо легче справиться.

например.,

// Change this:
d.Elements("INDEX")
 .Where(i => i.Attribute("name").Value == "Comment(idmComment)")
 .First()
 .Attribute("value").Value

// to this:
d.Elements("INDEX")
 .Where(i => i.Attribute("name").Value == "Comment(idmComment)")
 .Select(i => i.Attribute("value").Value)
 .First()

Я бы переписал это так:

private long processFile(StreamWriter oWriter, string inFileName)
{
    XDocument xmlDoc = XDocument.Load(inFileName);
    List<DocMetaData> docList = xmlDoc.Descendants("DOCUMENT")
        .Select(e => new
        {
            Folder = (string)e.Element("FOLDER").Attribute("name"),
            File = (string)e.Element("FILE").Attribute("filename"),
            Comment = (string)e.Elements("INDEX")
                .Where(i => (string)i.Attribute("name") == "Comment(idmComment)")
                .Select(i => (string)i.Attribute("value"))
                .FirstOrDefault(),
            Title = (string)e.Elements("INDEX")
                .Where(i => (string)i.Attribute("name") == "Title(idmName)")
                .Select(i => (string)i.Attribute("value"))
                .FirstOrDefault(),
            DocClass = (string)e.Elements("INDEX")
                .Where(i => (string)i.Attribute("name") == "Document Class(idmDocType)")
                .Select(i => (string)i.Attribute("value"))
                .FirstOrDefault(),
        })
        .ToList();
    OutputListToFile(oWriter, docList);
    return docList.LongCount();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...