开发者

C# LINQ question about foreach

is there any way to write this foreach in linq or another better way,

int itemNr = -1;
foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items) {

    itemNr++;
    foreach(ItemDetailDoc detail in itemDoc.Details) {
        int eventDocNr = -1;

        foreach(EventDoc eventDoc in detail.Events) {
            eventDocNr++;

            if(!eventDoc.HasEAN) {
                HideShowPanels(pMatch);
                txt_EAN.Text = String.Empty;

                lbl_Match_ArtName.Text = itemDoc.Name;
                lbl_ArtNr.Text = itemDoc.Number;
       开发者_运维问答         lbl_unitDesc.Text = eventDoc.Description;

                m_tempItemNr = itemNr;
                m_tempEventNr = eventDocNr;
                txt_EAN.Focus();

                return;
            }
        }
    }
}

I just think this is not the correct way to write it. please advise.


If itemNr and eventDocNr is not needed you could use:

var item =
            (from itemDoc in handOverDoc.Assignment.Items
             from detail in itemDoc.Details
             from eventDoc in detail.Events
             where !eventDoc.HasEAN
             select new 
                {
                    Name = itemDoc.Name,
                    Number = itemDoc.Number,
                    Description = eventDoc.Description 
                }).FirstOrDefault();

if (item != null)
{
    HideShowPanels(pMatch);
    txt_EAN.Text = String.Empty;

    lbl_Match_ArtName.Text = item.Name;
    lbl_ArtNr.Text = item.Number;
    lbl_unitDesc.Text = item.Description;

    txt_EAN.Focus();
}


No, I dont think there is a better way to do that. LINQ is about queries, you do quite a lot of processing in there. Unless you have a shortcut that is not obvious here.... this seems t obe the only way.

If you COULD start from the eventDoc - you could filter out those without EAN and then go from there backward, but Ican not say how feasible that is as I miss the complete model (as in: maybe you have no back lniks, so you would be stuck wit hthe eventDoc an dcoul dnot get up to the item.

First look that looks fine.


You could try the following LINQ:

var nonEANs = from ItemDoc itemDocs in itemDocList
              from ItemDetailDoc itemDetailDocs in itemDocs.Details
              from EventDoc eventDocs in itemDetailDocs.Events
              where !eventDocs.HasEAN
              select eventDocs;


foreach (var i in nonEANs)
{
    System.Diagnostics.Debug.WriteLine( i.HasEAN);
}

Should return 7 false EANs: I recreated you nested structures like this

List<ItemDoc> itemDocList = new List<ItemDoc>()
{
    new ItemDoc() 
    {
         Details = new List<ItemDetailDoc>()
         {
             new ItemDetailDoc()
             {
                  Events = new List<EventDoc>()
                  {
                      new EventDoc()
                      {HasEAN=false},
                      new EventDoc()
                      {HasEAN=false}
                  }
             },
             new ItemDetailDoc()
             {
                  Events = new List<EventDoc>()
                  {
                      new EventDoc()
                      {HasEAN=true},
                      new EventDoc()
                      {HasEAN=false}
                  }
             }
         }
    },
    new ItemDoc() 
    {
         Details = new List<ItemDetailDoc>()
         {
             new ItemDetailDoc()
             {
                  Events = new List<EventDoc>()
                  {
                      new EventDoc()
                      {HasEAN=false},
                      new EventDoc()
                      {HasEAN=false}
                  }
             },
             new ItemDetailDoc()
             {
                  Events = new List<EventDoc>()
                  {
                      new EventDoc()
                      {HasEAN=false},
                      new EventDoc()
                      {HasEAN=false}
                  }
             }
         }
    }
};


I think you are stuck with the for each loops as you need the itemNr and eventDocNr. You can use for loops to avoid increasing the itemNr and eventDocNr, but this does not reduce the number of loops.

Edit:

And if you do need the itemNr and eventDocNr try this:

var query = handOverDoc.Assignment.Items
                       .SelectMany(
                           (x, i) => x.Details.SelectMany(
                               (d, di) => d.Events.Where(x => x.HasEAN).Select(
                                   (e, ei) => new { 
                                       ItemIndex = di, 
                                       EventIndex = ei, 
                                       Detail = d,
                                       Event = e 
                                   }
                                )
                            )
                        );
foreach (var eventInfo in query) {
    HideShowPanels(pMatch);
    txt_EAN.Text = String.Empty;

    lbl_Match_ArtName.Text = eventInfo.Detail.Name;
    lbl_ArtNr.Text = eventInfo.Detail.Number;
    lbl_unitDesc.Text = eventInfo.Event.Description;

    txt_EAN.Focus();

    return;     
}

If you need only the first event with an EAN you could also use the following on the above query:

var item = query.FirstOrDefault();
if (item != null) {
    // do you stuff here
}


You can get the index in LINQ quite easily, for example :-

var itemDocs = handOverDoc.Assignment.Items.Select((h, i) => new { item = h, itemindex = i })

You can repeat this process for your inner loops also and I suspect you could then use SelectMany() to simplify it even further.


You're trying to do two different things here. Firstly you're trying to find a document, and secondly you're trying to change things based upon it. The first stage in the process is simply to clarify the code you already have, e.g.

(Note this takes into account previous comments that the computed indexes in the original code are not needed. The exact same type of split into two methods could be done whether or not the computed indexes are required, and it would still improve the original code.)

public void FindAndDisplayEventDocWithoutEAN(HandOverDoc handOverDoc)
{
    var eventDoc = FindEventDocWithoutEAN(handOverDoc);
    if (eventDoc != null)
    {
        Display(eventDoc);
    }
}

public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
    foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items)
        foreach(ItemDetailDoc detail in itemDoc.Details) 
            foreach(EventDoc eventDoc in detail.Events)
                if(!eventDoc.HasEAN)  
                    return eventDoc;
    return null;
}

public void Display(EventDoc eventDoc)
{
    HideShowPanels(pMatch);
    txt_EAN.Text = String.Empty;

    lbl_Match_ArtName.Text = itemDoc.Name;
    lbl_ArtNr.Text = itemDoc.Number;
    lbl_unitDesc.Text = eventDoc.Description;

    m_tempItemNr = itemNr;
    m_tempEventNr = eventDocNr;
    txt_EAN.Focus();
}

Once you've done that, you should be able to see that one method is a query over the main document, and the other is a method to display the results of the query. This is what's known as the single responsibility principle, where each method does one thing, and is named after what it does.

The transformation of the nested foreach loops to a linq query is now almost trivial. Compare the method below with the method above, and you can see how mechanical it is to translate nested foreach loops into a linq query.

public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
    return (from itemDoc in handOverDoc.Assignment.Items
            from detail in itemDoc.Details
            from eventDoc in detail.Events
            where !eventDoc.HasEAN
            select eventDoc).FirstOrDefault();
}


yet another spin...

var query = from itemDocVI in handOverDoc.Assignment
                                     .Items
                                     .Select((v, i) => new { v, i })
            let itemDoc = itemDocVI.v
            let itemNr = itemDocVI.i
            from detail in itemDoc.Details
            from eventDocVI in detail.Events
                                     .Select((v, i) => new { v, i })
            let eventDoc = eventDocVI.v
            let eventDocNr = eventDocVI.i
            where eventDoc.HasEAN
            select new
            {
                itemDoc,
                itemNr,
                detail,
                eventDoc,
                eventDocNr
            };

var item = query.FirstOrDefault();
if (item != null)
{
    HideShowPanels(pMatch);
    txt_EAN.Text = String.Empty;

    lbl_Match_ArtName.Text = item.itemDoc.Name;
    lbl_ArtNr.Text = item.itemDoc.Number;
    lbl_unitDesc.Text = item.eventDoc.Description;

    m_tempItemNr = item.itemNr;
    m_tempEventNr = item.eventDocNr;
    txt_EAN.Focus();
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜