tags:

views:

214

answers:

8

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.

+1  A: 

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
}
Obalix
But if i dont need the itemNr and eventDocNr anymore. is there a better way to write this ?
Tan
@Tan: see edit.
Obalix
Select has an overload that provides the index, see my answer.
Hightechrider
itemDoc is not declared so you won't be able to extract the Name and Number from it. This way you only have access to the eventDoc.
Cornelius
Thank you, Cornelius. I edited the post. Now the access to the positions and the ItemDoc is enabled.
Obalix
+2  A: 

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.

TomTom
Hi TomTom, you can also use LINQ to query in-memory object structures as well as LINQ to SQL/Entities.
Jason Roberts
Irrelevant - i did not go to that. problem is that if you ahve a list of eventDoc WITOUT pointers out back to the item, then querying this list wont give you the same stuff you have with this query.
TomTom
I'm afraid I don't agree that this is fine. Even if it's not rewritten as linq, it could still be significantly improved just by splitting into multiple methods to separate out the two actions he's doing as one block.
Greg Beech
+1  A: 

Hi Tan, 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}
                  }
             }
         }
    }
};
Jason Roberts
Not the same. Note how he needs the index in the loops for further operations.
TomTom
You would not have access to the Name and Description in itemDoc as only eventDoc get selected.
Cornelius
Does not allow access to the Name and Description of ItemDoc, therefore, it will not work with the code inside the loop.
Obalix
+3  A: 

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();
}
Cornelius
A: 

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.

Hightechrider
A: 

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();
}
Greg Beech
And where does he get itemNr and eventDocNr from?
Hightechrider
@Hightechrider - I already explained that I have not included them based on his comments to other answers, and that the main transformation into two methods is valid in either case. Setting module level state in queries is almost always the wrong approach, so I'm assuming that part is going to be improved to not need these indexes either.
Greg Beech
A: 

Thanks for all help guys u are the best!!!

Tan
A: 

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();
}
Matthew Whited