?
SolvedPrivate

Is this correct? comparing 2 lists with Except

Posted on 2014-08-16
97
Medium Priority
?
91 Views
Last Modified: 2016-02-18
Please see the 2 attached screenshots.

1. I have an original list and I get it like this:

 List<InputAttribute> origInputAttributes =  SaveInputAttributes(command.Id);

Open in new window


2. I have a new list

PersistSurveyFormCommand command;

Open in new window


3. They both have Id in common --- inputId for origInputAttributes and Id for command

4. I want to compare the 2 lists and see what has changed "command" list compared to the original list. For example, in the attached screenshots, "value" field has changed.

I'm not sure if this this correct. Don't think it is because the Ids are the same. I think I should be looking for Id and maybe the "value" field?

var commandInputs = command.Inputs.ToList();
// Get the list of Id's that are in the command list
List<int> cIDs = commandInputs.Where(i => i.Id.HasValue).Select(c => c.Id).Cast<int>().ToList();

// Get the list of Id's that are in the original list
List<int> fIDs = formInputs.Select(f => f.InputId).ToList();

 // Get a list of Ids that are changed? //**** I don't think this part is correct
            List<int> FNoMatchingC = fIDs.Except(cIDs).ToList();

Open in new window

origList.png
newList.png
0
Comment
Question by:Camillia
  • 62
  • 24
  • 4
  • +1
93 Comments
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40265143
Hi Camilla;

In order to use Except method the two list must be of the same type. The reason for this is that Except along with others such as Intersect and Union are mathematical set operation. From what I can tell your two sets are not that. That said you can make them the same. For example assume you have the following to types that you wish to use Except on. Note that the property names are not spelled the same or have different capitalization and on may have extra properties.
public class ObjectA
{
    public int InputId { get; set; }
    public string name { get; set; }
    public string value { get; set; }
}

public class ObjectB
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
    public string Department { get; set; }
}

Open in new window


The first step to make the two like types is to use only properties that need to be compared and that show up in the other object, as in the above example ObjectB has a Department property that is NOT in ObjectA and so it can NOT be used.

Below are two list of the above types
List<ObjectA> objA = new List<ObjectA>() {
    new ObjectA() { InputId = 1, name = "Tom", value = "XYZ" },
    new ObjectA() { InputId = 2, name = "James", value = "ABC" },
    new ObjectA() { InputId = 3, name = "Alice", value = "DEF" }
};

List<ObjectB> objB = new List<ObjectB>() {
    new ObjectB() { Id = 1, Name = "Tom", Value = "XYZ", Department = "IT" },
    new ObjectB() { Id = 2, Name = "James", Value = "AAC", Department = "Management" },
    new ObjectB() { Id = 4, Name = "Samantha", Value = "LMN", Department = "Programming" }
};

Open in new window


To make them of like type we can create them as Anonymous type having the same structure. In order for two objects of Anonymous types to be considered types of the same Anonymous type the corresponding properties must be in the same order and must be spelled the same way and must be in the same capitalization and same data type. The following two list are of the same Anonymous type. Note the property names on the right of assignment statenebts may be different on the right.
var objAModified = objA.Select(o => new { 
    InputId = o.InputId,
    Name = o.name,
    Value = o.value
}).ToList();

var objBModified = objB.Select(o => new { 
    InputId = o.Id,
    Name = o.Name,
    Value = o.Value
}).ToList();

Open in new window


Now that all that work was done you can do this. The variable results will contain a list of all objBModified that have been modified from the list objAModified.

var results = objBModified.Except(objAModified);

Open in new window


From the above data the variable results contain the following records. Where InputId 2 has modified values and InputId 4 does not appear in the original list.
InputId Name      Value
2       James     AAC 
4       Samantha  LMN 

// Find all records that need either to be added or modified
var needMod = (from res in results
               join oA in objA on res.InputId equals oA.InputId into modGroup
               from mg in modGroup.DefaultIfEmpty()
               select new { res, mg }).ToList();

Open in new window


Now update the original list as needed from the needMod list              
foreach (var rec in needMod)
{
    // See if the current rec needs to be added or modify
    if ( rec.mg == null )
    {
        // Record needs to be added to original list because it has a null value in mg
        var newRec = new ObjectA();
        newRec.InputId = rec.res.InputId;
        newRec.name = rec.res.Name;
        newRec.value = rec.res.Value;
        objA.Add(newRec);
    }
    else
    {
        // Because it has a non null mg record.
        // Get the existing record in objA and modify it
        var currentRec = objA.Find( oA => oA.InputId == rec.mg.InputId );
        currentRec.name = rec.res.Name;
        currentRec.value = rec.res.Value;
    }
}               

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40265163
Thanks Fernando.  I thought this will be simpler. This goes back to the question you helped me with the other day. I still need to fix another section. In that section, code deletes 3000 rows and inserts the entire thing again. It should actually update in place. I tried to update but got an Entity Framework error that I didn't understand...

So, I want to do it like above....find the difference. Then delete the rows that are old and insert the new rows. Probably not a good method but I have to get this done by Monday.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265193
Read your post. Let me try it.  I wonder why I can't just update and get that Entity Framework error. I'm thinking maybe because I don't delete the rows first but I don't know.

 I'll trace the code for that error and try your code as well.  I will post back. Thanks again for your help.
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 7

Author Comment

by:Camillia
ID: 40265443
This is what I have for the anonymous types. I will continue and post back:

  var origList = origInputAttributes.Select(o => new
            {
                InputId = o.InputId,
                Name = o.Name,
                Value = o.Value
              
            }).ToList();


            var newList = command.Inputs.Select(o => new
                {
                    InputId = o.InputProperties.Select(p => new
                      {
                          InputId = p.Id,
                          
                      }),

                    Name = o.InputProperties.Select(p => new
                    {
                        Name = p.Name

                    }),

                    Value = o.InputProperties.Select(p => new
                    {
                        Value = p.Value

                    }),
                });

Open in new window

0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40265504
Hi Camilla;

This part of your last post will be a problem. Although the Anonymous type properties are spelled, have the same capitalization and in the same order as the first Anonymous type they do not have the same data types as each of the properties of the first one.
var newList = command.Inputs.Select(o => new
              {
                  InputId = o.InputProperties.Select(p => new
                    {
                        InputId = p.Id                        
                    }),
                  Name = o.InputProperties.Select(p => new
                  {
                      Name = p.Name
                  }),
                  Value = o.InputProperties.Select(p => new
                  {
                      Value = p.Value
                  }),
              });

Open in new window

             
For exaample this line in the above code which the other two lines also suffer from being a collection and NOT a single instance of a string and therefore are NOT of the same Anonymous type and can't be used in a set operations.
// This line of code returns a IEnumerable<string>.
InputId = o.InputProperties.Select(p => new
          {
              InputId = p.Id                        
          }),    

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40265512
So, there's no solution to this?? What else can I do?? Deleting 3000 rows and reinserting isn't working...timing out the app. This anonymous type isn't working. Entity Framework throws an error when I want to update in place.

I'm at a loss with this code. I'm new at this job and I don't want to tell them I don't know how to do this. I guess I have to.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40265520
Hi Camilla;

Can you please post the class definition that is contained in the collection inputs from command.Inputs.

This can be solved. Also which objects are Entity Framework objects and what is the Entity Framework exception that you are getting?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265547
This is "command"

 public class PersistSurveyFormCommand : PersistFormCommandBase
    {
        public string Header { get; set; }
        public int SurveyTypeId { get; set; }
        public int VersionNumber { get; set; }
        public int LanguageId { get; set; }
        public Dictionary<object, List<int>> ManufacturerProductCategoryMaps { get; set; }
    }

Open in new window



"Inputs" is here

public class PersistFormCommandBase
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string OrganizationName { get; set; }
        public int LabelPlacementId { get; set; }
        public string SuccessUrl { get; set; }
        public string SuccessAlert { get; set; }
        public string ErrorUrl { get; set; }
        public string ErrorAlert { get; set; }
        public string FormHtml { get; set; }
        public string SubmitButtonText { get; set; }
        public List<FormInput> Inputs { get; set; }
        public int AccountId { get; set; }
        public int FormTypeId { get; set; }
        public string FormStyle { get; set; }
        public string NameStyle { get; set; }
        public string DescriptionStyle { get; set; }
    }

Open in new window



This is "FormInput"

    public class FormInput
    {
        public int? Id { get; set; }
        public int InputTypeId { get; set; }
        public int DisplayOrder { get; set; }
        public List<FormInputProperty> InputProperties { get; set; }
    }

Open in new window


And this is the "FormInputProperty" where Id, Name and Value are:


   
 public class FormInputProperty
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }
    }

Open in new window


As for the EntityFramework error, if I can get the anonymous type working, it can get me going. I think I get the EF error because I try to insert the same rows..and not update. I don't know how the code does updates with EF. I will post the EF fix and the error that I first tried as well. But this anonymous fix should work as well. EF might be a better solution.

I will post EF error and what I tried in an hour or so.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40265567
I just need to know what the data type is stored in the property Input as in this command, command.Inputs. If you are not sure hover your cursor over the word inputs and a tool tip should appear which will give you the info I need.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265587
I misunderstood. See attached screenshot please
code.png
0
 
LVL 36

Expert Comment

by:ste5an
ID: 40265600
Why do you want to compare those two types? Comparing them means they have something in common. In a clean OOP design, this means these types should have either an interface or a base class in common...
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265627
I want to compare the data saved in them. If there are any differences, I use the ID to update or do whatever in the database. I will post the EF code and the error soon. That's another option as well to see if that would work. The orig developer wasn't sure if it would.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265676
I tried the EntityFrame work. Code originally deletes the rows and re-adds them. I commented the code that deletes. I added this but this adds rows...doesn't update

 commandInput.InputProperties.ForEach(p =>
                    {
                        var property = new InputAttribute();

                        property.Name = p.Name;
                        property.Value = p.Value;
                        ......
                        input.InputAttributes.Add(property);
                    });

This line of code, for example, updates the "input" object . I traced the code and it calls "commit" but can't step into it. It's EF code. Not sure how this object is update but the "inputAttributes" can't be updated. I think it's the design...there's no update method to update the inputAttributes...but there's a method to update "input". Maybe that's why the orig developer deletes the rows and re-adds them.

input.UpdateDate = DateTime.Now;

Open in new window



If we can get the anonymous type code working, that would get me going. I'll keep digging into the EF and see if I can update in place.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265697
I have one more idea if the anonymous type doesn't work. I wonder if I can pass the InputAttributes list to a stored proc and update the rows like that. I'll look into it.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40265771
Ok, spent some more time on this for my other ideas. There's a "sql bulk insert" in the code that I implemented but that won't be efficient either. I still would need to delete 3000+ rows and re-insert them.

The only option I see is to get the anonymous type code working. That would help me figure out which records are changed and need to be updated.

I'll await your response. Thanks for your help.
0
 
LVL 84

Expert Comment

by:David Johnson, CD, MVP
ID: 40265856
I still would need to delete 3000+ rows and re-insert them.

I think you should step back and re-examine your problem.  What do you have?  an xml file and a a database? What do you want? What are you trying to accomplish?
0
 
LVL 36

Expert Comment

by:ste5an
ID: 40266024
Code originally deletes the rows and re-adds them.

Sounds like a perfect task for a stored procedure.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266076
Hi Camilla;

Please post the complete code for the area you are trying to refactor so I can get a better understanding of what needs to be done.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266103
Sounds like a perfect task for a stored procedure.

Open in new window

Eventually, yes.

I think you should step back and re-examine your problem.  What do you have?  an xml file and a a database? What do you want? What are you trying to accomplish?

I have examined it. The orig developer deletes rows and reinserts them. They should be updated...not deleted and re-inserted.

Fernando - going to post the code now.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266116
Fernando, this is the code

1. It first deletes the rows. That's what I don't want to do anymore

 if (command.Id != 0) ClearInputAttributes(command.Id);

Open in new window


2. I get the list of the original attributes
 List<InputAttribute> origInputAttributes =  SaveInputAttributes(command.Id);

Open in new window


3. "command" holds the current changes to the form and the changes to the attributes. Since all the rows were deleted, code loops and reinserts everything. There could be 3000+ rows and it times that. This is what I don't want to do. I want to compare the original list to the "command"..see what was changed and just updated the rows that were changed.



 private void UpdateInputs(SurveyForm form, PersistSurveyFormCommand command, List<InputAttribute> origInputAttributes)
        {
             foreach (var commandInput in command.Inputs.ToList())
            {

               commandInput.InputProperties.ForEach(p =>
                  {
                      var property = new InputAttribute();

                      property.Name = p.Name;
                      property.Value = p.Value;
                      if (p.Name.EndsWith("weight", StringComparison.CurrentCultureIgnoreCase))
                      {
                          property.IsWeight = true;
                      }
                      property.UpdateDate = DateTime.Now;
                      property.UpdateAccountId = command.AccountId;
                      property.CreateAccountId = command.AccountId;
                      property.CreateDate = DateTime.Now;
                      
                      input.InputAttributes.Add(property);

           }

     }

Open in new window

0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266166
Hi Camilla;

Can you zip the project and upload it to your OneDrive and post the link here, this is just too complicated to be troubleshooting something like this without all the code. Thanks
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266175
It has 7 something projects in the solution. Can I attach the class here? If not, I'll try the zip. I don't have a OneDrive account.

Wonder if you can share screen with me on Skype. I did that with a developer in another company the other day.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266198
I've attached the class here. I see a OneDrive icon on my desktop. I've never used OneDrive. I just copy files to it??
 If all fails, let's skype.
class.txt
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266204
creating a onedrive account. Let you know when I upload the code
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266207
OK, when you upload to OneDrive make sure to place the file in a Public folder and post the link to here otherwise I will not be able to get to it.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266211
ok, thanks
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266233
Can you see it

https://onedrive.live.com/redir?resid=C81263191C1E80A3%21197

I'm stepping out for one hour. I'll check back. If you do a search for "camilla"...you'll see the place in the code I'm working on.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266244
Got it. Let me see what I can figure out.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266284
Thanks. I'm looking at BulkCopy...so still delete but use bulkcopy. It shouldn't be deleting to begin with.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266310
Camilla, the code you posted to OneDrive seems to be a mixture of the original code and mods that you have made to it and it is hard to follow. How is it determine if something needs to be added or that is already in the 3000+ list?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266325
How is it determine if something needs to be added or that is already in the 3000+ list?

Open in new window


It's not determined. That's what I want to figure out. That's why I made those mods. So I tried to get the orig list.

Look at this routine:

In here, I added one line of code to get the original list. The rest are the orig code. The first Line of code deletes all the rows from the database


 public override void Handle(PersistSurveyFormCommand command)
        {
            isNew = command.Id == 0;

            //I added this
            List<InputAttribute> origInputAttributes =  SaveInputAttributes(command.Id);
            //

            SurveyForm form = GetForm(command); //This deletes all the rows

            UpdateInputs(form, command, origInputAttributes); //I pass the OrigInputAttributes to the routine. 
            SetInputPages(form, command);

            PersistForm(form);

            CompileForm(form, command.AccountId);

            uow.Commit();

            command.Id = form.Id;
        }

Open in new window



This is the original code. I removed my changes and added notes where it re-inserts the 3000+ rows. Because it deletes all the rows in "GetForm"...it just re-inserts them. No determination is made. That's what I want to do...I want to determine what was changed. I want to compare what was in the  "origInputAttributes" to what's new in "command" and just to an update. OR...if we can get the anonymous type done...I can just delete the differences and re-insert those. No need to delete 3000 (all the rows) from the database.

So in "UpdateInputs"...I want to do that determination.

 private void UpdateInputs(SurveyForm form, PersistSurveyFormCommand command, List<InputAttribute> origInputAttributes)
        {
           
           /*
            var formInputs = form.Form.Inputs.ToList();

            //foreach input that exists on the form and does not exist on the command, remove
            foreach (var formInput in formInputs)
            {
                if (!command.Inputs.Any(i => i.Id == formInput.Id))
                {
                    formInput.IsActive = false;
                    formInput.UpdateDate = DateTime.Now;
                    formInput.UpdateAccountId = command.AccountId;
                }
            }
            */
            

            //new refactored code
            
            var formInputs = form.Form.Inputs.ToList();
            // The command.Inputs.ToList() is also used in the lower part of the function
            // change that to commandInputs
            var commandInputs = command.Inputs.ToList();

            // Get the list of Id's that are in the form list
            List<int> fIDs = formInputs.Select(f => f.Id).ToList();

            // Get the list of Id's that are in the command list
            List<int> cIDs = commandInputs.Where(i => i.Id.HasValue).Select(c => c.Id).Cast<int>().ToList();

            // Get a list of form Id's that are NOT in the command's
            List<int> FNoMatchingC = fIDs.Except(cIDs).ToList();

            // Get all the SurveyForm that needs to be updated
            var formAllInputs = formInputs.FindAll(f => FNoMatchingC.Contains(f.Id));
            

            // Update the SurveyForm that need updating
            foreach (var formInput in formAllInputs)
            {
                formInput.IsActive = false;
                formInput.UpdateDate = DateTime.Now;
                formInput.UpdateAccountId = command.AccountId; 
            }
            //end of new refactored code
                        
            //foreach input that exists on the command and not on the form, add
            foreach (var commandInput in command.Inputs.ToList())
            {

               
                var input = formInputs.FirstOrDefault(i => i.Id == commandInput.Id);
              
                if (input == null) //new input on the form
                {
                    input = new Input();
                    input.CreateDate = DateTime.Now;
                    input.CreateAccountId = command.AccountId;
                    form.Form.Inputs.Add(input);

                   

                }
                else
                {
                    input.UpdateAccountId = command.AccountId;
                    input.UpdateDate = DateTime.Now;

                    
                 
                }

                input.IsActive = true;
                input.FormId = form.FormId;
                input.DisplayOrder = (byte)commandInput.DisplayOrder;
                input.InputTypeId = commandInput.InputTypeId;

               
                // The attribute list gets cleared in the GetForm method, so we
                // can just add them here.
        
             //**** here....this is where it reinserts the InputAttributes because GetForm deleted all of them. No determination is made.
              commandInput.InputProperties.ForEach(p =>
                  {
                      var property = new InputAttribute();

                      property.Name = p.Name;
                      
                      
                      property.Value = p.Value;
                      if (p.Name.EndsWith("weight", StringComparison.CurrentCultureIgnoreCase))
                      {
                          property.IsWeight = true;
                      }

                      property.CreateDate = DateTime.Now;
                      property.UpdateDate = DateTime.Now;
                      property.CreateAccountId = command.AccountId;
                      property.UpdateAccountId = command.AccountId;
                      
                      
                      
                      input.InputAttributes.Add(property);
                  });
                 
                 
                 
            }
        }

Open in new window

0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266331
When I say this
How is it determine if something needs to be added or that is already in the 3000+ list?
I mean that you have two collections one of FormInput and the other collection of Input. What properties of each will determine that it is already in the 3000+ list or that it needs to be added?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266341
Oh, let me take a look at FormInput reall quick. I don't think that's related to the other collection but let me double check.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266421
I'm still testing this and taking some screenshots. Will post back soon.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266450
I mean that you have two collections one of FormInput and the other collection of Input. What properties of each will determine that it is already in the 3000+ list or that it needs to be added?

Ok, I tested have some screenshots.

1. "FormsInput"...it's the "InputAttributes" that holds the old/original attributes.
2. "commandInputs" holds the new attributes that are going to be inserted.

3. Note:  "FormInput" will have nothing in "inputAttributes" because code first deletes all the attributes. When I comment out the delete line of code...I can see all the original InputAttribute values. This is good because I don't think I need my code to get the original list.

So, can FormInput and CommandInputs be compared to see what's was changed? and remove the line that deletes the rows? I think it can. I hope so. Even if I can get the rows that need to be updated/changed...that would be good.

I've attached screenshots.
Command.png
FormsInput-If-Attributes-are-not-deleted
Form-With-Attributes-deleted.png
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266612
I used BulkCopy (bulkCopy.WriteToServer) but this required , again, deleting the 3000+ rows and it times out.

There has to be a way to compare what was on the previous form and on the new form...find the differences and just update those (or only delete the differences and reinsert)
0
 
LVL 84

Expert Comment

by:David Johnson, CD, MVP
ID: 40266725
form1.txt is already in the database correct?  You want to insert changed records from form2.txt ?

then for each line in form2 query the database and see if it is not equal if so update that record. else continue
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266730
Code first deletes the rows for some reason. Let me think. That's what I was getting at originally...and that's why Fernando was suggesting anonymous types.

I'll take a look at it again. It's consumed me all weekend.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266738
david, I doubt that will be efficient. I have 3000+ rows that I also need to check to see if it needs updating.

I think best is to compare the old list to the new...see which rows need updated and just update those. No need to keep looping.  This is what I can't figure out.

This is just not a good design and my manager has asked me not to say that in front of other developers. I won't say it but I wish I could just solve it by now.
0
 
LVL 84

Expert Comment

by:David Johnson, CD, MVP
ID: 40266745
you have 2 loops an inner and an outer loop which results in loop1.rows^loop2.rows comparisons either way you should be using an update and not a delete but you still need to query the database

or use a diff function to compare the two files and only export the ones that are different, then do a select on the database and then an update using the diff output.
http://support.microsoft.com/kb/320346/en-us
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266749
They're not files. They're List controls. List of objects. The files I've attached above are just screenshots of what's in the objects.
0
 
LVL 84

Expert Comment

by:David Johnson, CD, MVP
ID: 40266765
where do the objects come from?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266770
I think you, me and Fernando should be able to figure this out :)

Look at my post ID: 40266450  and the screenshots.

I just want to see if we can compare the 2 objects and grab what's different. The objects are populated from the database. But the issue is that first rows are deleted and re-inserted.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40266790
Hi Camilla;

This code should run but could not test. Please read all comments in the code.

// it's the "InputAttributes" that holds the old/original attributes. 
// Use full namespace to InputAttribute
List<Aimbase.FormBuilder.Domain.Entities.InputAttribute> formInputAttribute = formInputs.Select(l => l.InputAttributes).ToList<Aimbase.FormBuilder.Domain.Entities.InputAttribute>();

//  Holds the new attributes that are going to be inserted.
var commandInputAttributes = new List<Aimbase.FormBuilder.Domain.Entities.InputAttribute>();

// Copy the InputAttribute from the command input to a new structure used by the
// original list so that they are of the same type and the Except method can be used on them
foreach ( FormInput fin in commandInputs )
{
    foreach ( FormInputProperty finp in fin.InputProperties )
    {
        InputAttribute inpA = new InputAttribute();
        inpA.Id = finp.Id;
        inpA.Name = finp.Name;
        inpA.Value = finp.Value;   
        commandInputAttributes.Add(inpA);
    }
}

// Find all the commands that need to be added or modified.
// There is a class that is needed at the end of this code to do the comparisons.
var modList = commandInputAttributes.Except(formInputAttribute, new InputAttributeEqualityComparer());

// Seperate those that need to be added and those that need modifying
var needMod = (from mod in modList
               join orig in formInputAttribute on mod.Id equals orig.Id into modGroup
               from mg in modGroup.DefaultIfEmpty()
               select new { mod, mg }).ToList();

// Do the update and mod here
foreach (var rec in needMod)
{
    if (rec.mg == null)
    {
        // Record needs to be added to original list
        // rec.mod is of the correct type and can be added
        // fill any uninitialized properties here.
        formInputAttribute.Add(rec.mod);
    }
    else
    {
        // Get the existing record in formInputAttribute and modify it
        var currentRec = formInputAttribute.Find(orig => orig.Id == rec.mg.Id);
        currentRec.Name = rec.mg.Name;
        currentRec.Value = rec.mg.Value;
    }
}  


// This class is needed to do the comparison by the Except method not all 
// properties are filled, only Id, Name and Value are used.
public class InputAttributeEqualityComparer : IEqualityComparer<InputAttribute>
{

    public bool Equals(InputAttribute x, InputAttribute y)
    {
        if ( x.Id == y.Id && x.Name == y.Name && x.Value == y.Value )
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public int GetHashCode(InputAttribute obj)
    {
        int hCode = obj.Id.GetHashCode() ^ obj.Name.GetHashCode() ^ obj.GetHashCode();
        return hCode;
    }
}

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40266797
Thank you so much, Fernando. Let me take a look and try it.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266800
So, this code replaces that "foreach" loop?? which section from the orig code do I need to remove and replace with this?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266816
Ok, let me implement it and I think I will get a better understanding of it and what should be replaced and how it should work. I will post back.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40266864
Howcome I get the attached error? I even retyped it just in case.
error.png
0
 
LVL 64

Accepted Solution

by:
Fernando Soto earned 2000 total points
ID: 40267739
Hi Camilla;

See if these changes get you what you need.

// it's the "InputAttributes" that holds the old/original attributes. 
// ========================== Change made here =======================
// Remove old variable formInputAttribute and replace with the blow line
List<Input> formInputAttributeList = formInputs.ToList();
// ===================================================================
//  Holds the new attributes that are going to be inserted.
var commandInputAttributes = new List<Aimbase.FormBuilder.Domain.Entities.InputAttribute>();

// Copy the InputAttribute from the command input to a new structure used by the
// original list so that they are of the same type and the Except method can be used on them
foreach ( FormInput fin in commandInputs )
{
    foreach ( FormInputProperty finp in fin.InputProperties )
    {
        InputAttribute inpA = new InputAttribute();
        inpA.Id = finp.Id;
        inpA.Name = finp.Name;
        inpA.Value = finp.Value;   
        commandInputAttributes.Add(inpA);
    }
}

// ========================== Changes made here =======================
// formInputAttributeList is a List of Input and each Input has a List of InputAttributes
// and the reason for the error in my last post.
foreach (Input finput in formInputAttributeList)
{
    // Get List of InputAttributes from the current Input object
    List<InputAttribute> formInputAttribute = finput.InputAttributes.ToList();
    var modList = commandInputAttributes.Except(formInputAttribute, new InputAttributeEqualityComparer());

    var needMod = (from mod in modList
                   join orig in formInputAttribute on mod.Id equals orig.Id into modGroup
                   from mg in modGroup.DefaultIfEmpty()
                   select new { mod, mg }).ToList();

    foreach (var rec in needMod)
    {
        if (rec.mg == null)
        {
            // Record needs to be added to original list
            // rec.mod is of the correct type and can be added
            // fill any uninitialized properties here.
            formInputAttribute.Add(rec.mod);
        }
        else
        {
            // Get the existing record in formInputAttribute and modify it
            var currentRec = formInputAttribute.Find(orig => orig.Id == rec.mg.Id);
            currentRec.Name = rec.mg.Name;
            currentRec.Value = rec.mg.Value;
        }
    }
}



// This class is needed to do the comparison by the Except method not all 
// properties are filled, only Id, Name and Value are used.
public class InputAttributeEqualityComparer : IEqualityComparer<InputAttribute>
{

    public bool Equals(InputAttribute x, InputAttribute y)
    {
        if ( x.Id == y.Id && x.Name == y.Name && x.Value == y.Value )
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public int GetHashCode(InputAttribute obj)
    {
        int hCode = obj.Id.GetHashCode() ^ obj.Name.GetHashCode() ^ obj.GetHashCode();
        return hCode;
    }
}

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40267758
Thanks, let me try.  I really appreciate all the help this weekend. That was beyond being nice of you.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40268779
What is the meaning of this? "mg" decides of rows are new or old rows that need to be updated?


var needMod = (from mod in modList
                               join orig in formInputAttribute on mod.Id equals orig.Id into modGroup
                               from mg in modGroup.DefaultIfEmpty()
                               select new { mod, mg }).ToList();

Open in new window

0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40268806
mod is the command input that is being checked to see if it is in the 3000+ list of formInputAttribute. If for every member of result set needMod has both a mod and a mg, meaning that mg is NOT null, then you need to update that object from the 3000+ list. If mg is null that means that the command input is NOT in the list of 3000+ list and needs to be added.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40268963
It might a tweak but I'm not sure. (If you think this is too much hassle, I can just leave it as is and go with BulkInsert. You've helped a lot already)

I've attached 4 screenshots.

1. I don't delete the rows. When I bring up the page, I see the rows that I already have. This is good.
2. I made some changes to the same data on the screent. Not adding any new rows.
3. Code goes thru "if (rec.mg == null)" section. But this is an update...not a new insert
1.png
2.png
3.png
4.png
0
 
LVL 7

Author Comment

by:Camillia
ID: 40268999
I don't think this ever gets called

  public class InputAttributeEqualityComparer : IEqualityComparer<InputAttribute>

I put a debug step and press F11 and didn't see it stop there. I can try it again later
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40269364
Hi Camilla;

The below code looks at the results of the query just above it in the code. True part of it is adding record and false part is updating records already there.

    foreach (var rec in needMod)
    {
        if (rec.mg == null)
        {
             //  If rec.mg == null is true this is an INSERT and this part of the if statement gets executed.
            // ======================================================================== 
            // Record needs to be added to original list
            // rec.mod is of the correct type and can be added
            // fill any uninitialized properties here.
            formInputAttribute.Add(rec.mod);
        }
        else
        {
            // If this section is executed the record gets updated 
            // ============================================
            // Get the existing record in formInputAttribute and modify it
            var currentRec = formInputAttribute.Find(orig => orig.Id == rec.mg.Id);
            currentRec.Name = rec.mg.Name;
            currentRec.Value = rec.mg.Value;
        }
    }

Open in new window


If the results of the Linq query, the result which is stored in needMod, is NOT an empty list then this class public class InputAttributeEqualityComparer : IEqualityComparer<InputAttribute> and more specifically the Equal method is being called because you state to the method to use it in the parameter list of the call. One important thing to note here is that there are two properties which determine if it is a new attribute or a modified one and those two properties are Name and Value. The Id if it were to change would be most likely a new attribute because it would not be in the list of old attribute. So if something other then that is changing which is not part of those two properties then you have an issue.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40269374
. One important thing to note here is that there are two properties which determine if it is a new attribute or a modified one and those two properties are Name and Value. The Id if it were to change would be most likely a new attribute because it would not be in the list of old attribute. So if something other then that is changing which is not part of those two properties then you have an issue.

This could be it. I'll double check this.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270393
(I don't know why a "request for attention has been filed" is on top of this ticket)

I compared modInput and CommandInputAttributes and there are no changes in the 7 attributes. I've attached screenshots of both lists and the 7 attributes. So, it shouldn't go down the "insert" section of the code above because there's nothing new to add.
ModList.docx
CommandInputAttributes.docx
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270411
I think this line

 
int hCode = obj.Id.GetHashCode() ^ obj.Name.GetHashCode() ^ obj.GetHashCode();

Open in new window


should be  this (it was missging obj.Value.GetHashCode(); )

 int hCode = obj.Id.GetHashCode() ^ obj.Name.GetHashCode() ^ obj.Value.GetHashCode();

Open in new window


Going to try this again. Will post back
0
 
LVL 36

Expert Comment

by:ste5an
ID: 40270439
Without testing: Your hash looks not perfect.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270448
yeah, got an error object reference not set.

Those 2 lists look the same. Code shouldn't go down the "insert" section of the if statement. I'll keep debugging to see if i can figure it out.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40270466
Are you changing items that InputAttributeEqualityComparer is NOT testing for equality on?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270487
No, I bring up the page. I don't change anything. I only click save. Stepped thru it and captured the screenshots in the 2 word docs. The 7 InputAttributes are the same.

I did F11 and I don't see it go to this routine. I see it call the hash routine but not the one below. I'll debug again now.

public bool Equals(InputAttribute x, InputAttribute y).
 {
      .........
  }
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270517
I think it's because formInputAttribute is empty (count=0). Then goes to this line, compares and sees all the rows as new

var modList = commandInputAttributes.Except(formInputAttribute, new InputAttributeEqualityComparer());

Open in new window


See attached screenshot. So, why is CformInputAttribute is empty?? going to debug again.
formInputAttribute.png
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40270527
If you bring up the form and change two  values, Name and Value, does it update the list?
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270532
let me try it now.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270604
1. I made one change. "CommandInputAttriubtes" holds that change in one of the 7 attributes rows.

2. In this foreach, there are 18 rows. The "InputAttribute" is count zero. Screenshot attached.

foreach (Input finput in formInputAttributeList)
  {
  }

3. So, this has a count of zero which marks all the rows as new.

List<InputAttribute> formInputAttribute = finput.InputAttributes.ToList();

Open in new window


4. Which bring us to this line. I don't think the code holds the original inputAttributes. This is because the assumption is just to delete everything when save is clicked.

If you scroll up to my first original post above, I was getting the original list before being deleted. I was saving it in this:  List<InputAttribute> origInputAttributes =  SaveInputAttributes(command.Id);

Can I use this object instead of formInputAttributeList? I think I can.


  //// it's the "InputAttributes" that holds the old/original attributes. 
              List<Input> formInputAttributeList = formInputs.ToList();

Open in new window

FormInputAttribute-count-Zero.png
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270662
Yeah, I think I might be able to get this working if I save the original inputAttributes.

I have this List<InputAttribute> origInputAttributes = SaveInputAttributes(command.Id);

But the foreach has a different type. If I can get this changed and working, I think it should work.

 foreach (Input finput in origInputAttributes)  // formInputAttributeList
            {
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40270915
Hi Camilla;

To your statement, "1. I made one change. "CommandInputAttriubtes" holds that change in one of the 7 attributes rows.", OK, this is good.

To the statement, "2. In this foreach, there are 18 rows. The "InputAttribute" is count zero. Screenshot attached.", well if InputAttribute from formInputAttributeList is zero then this is bad because then there is nothing to compare to and therefore everything from the other list will be considered new.

to this statement, "3. So, this has a count of zero which marks all the rows as new.", this is because of what happened above. How come the input attributes are zero when there could be a possibility of 3000+ attributes? Were they deleted?

To this statement, "4. Which bring us to this line. I don't think the code holds the original inputAttributes. This is because the assumption is just to delete everything when save is clicked. ", Well can you comment out the code that does the deletion?

To this statement, "Can I use this object instead of formInputAttributeList? I think I can., List<Input> formInputAttributeList = formInputs.ToList();". this is a ICollection<Input> which is not what you need. You need a collection of inputAttributes. To get that from formInputs.ToList() you need to drill down more to something like formInputs. InputAttributes.

Yeah, I think I might be able to get this working if I save the original inputAttributes.
I have this List<InputAttribute> origInputAttributes = SaveInputAttributes(command.Id);
But the foreach has a different type. If I can get this changed and working, I think it should work.
foreach (Input finput in origInputAttributes)  // formInputAttributeList
{
From the code I saw there are more then one Input objects and each one of these Input object holds a List<InputAttribute>. Remember doing set operation both list must be of the same type and the reason for creating a List<InputAttribute> from the command input
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270969
to this statement, "3. So, this has a count of zero which marks all the rows as new.", this is because of what happened above. How come the input attributes are zero when there could be a possibility of 3000+ attributes? Were they deleted?

I think they just don't populate with the assumption that it will get deleted later. I'll see if I can find the section of the code and get it populated. If I can, then the rest of your code should be fine.

Yes, I now understand the explanation for the ICollection and why I need to drill down. I'll dig deeper.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40270974
To this statement, "4. Which bring us to this line. I don't think the code holds the original inputAttributes. This is because the assumption is just to delete everything when save is clicked. ", Well can you comment out the code that does the deletion?

Yes, I did that as well..commented the line of deletion out and it still wasn't populated. I'm going to trace the code but I think they just don't populate it on the assumption that it will get deleted later.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40271014
tracing it again to see when the form loads...does it have the inputAttributes in it. I've also commented out the row that deletes the inputAttributes to see what goes on.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40271071
Yeah, seems like inputAttributes just don't get populated, so this like has everything but the inputAttributes. Probably the assumption is it will get repopulated. The inputAttributes are deleted from database and repopulated from the screen...whatever the user types and then reinserted. But, even without deleting them, code just doesn't populate the inputAttributes.

var formInputs = form.Form.Inputs.ToList();  --- so this code has no orig inputAttributes.

I wonder how I can populate form.Form.Inputs.InputAttributes collection.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40271110
Maybe I can populate it like this. Going to a meeting and will test it later


 var formInputs = form.Form.Inputs.ToList();
            List<InputAttribute> origInputAttributes = SaveInputAttributes(command.Id); //camilla see if this can populate inputAttributes.

            foreach (var row in formInputs)
            {
                foreach (var ia in origInputAttributes)
                {
                    row.InputAttributes.Add(ia);
                }
            }

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40271146
Looks like that code gets the form populated with the orig InputAttributes. Screenshot attached. I'll see if the comparison works now.

I'll post back.
populated.png
0
 
LVL 7

Author Comment

by:Camillia
ID: 40271256
I can't use the "Id" looks like it. This is because when I click "save", the new attributes won't have an Id.  I have to trace it and see what other field I can use.

Is this correct, if I remove the id?

public bool Equals(InputAttribute x, InputAttribute y)
            {
                if (x.Name == y.Name && x.Value == y.Value)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public int GetHashCode(InputAttribute obj)
            {
                int hCode = obj.Name.GetHashCode() ^ obj.GetHashCode();
                return hCode;
            }

Open in new window

0
 
LVL 7

Author Comment

by:Camillia
ID: 40271327
Now that  I think about it, there has to be an Id to compare the rows but I only have the IDs when I get the orig rows from the database. Looks like there's no Id when I click "save" because code originally deletes the rows.

I'll step thru it again but looks like there's no Id. I'll post back.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40273092
Hi Camilla;

You state, "I think they just don't populate with the assumption that it will get deleted later.", well if they are not populated assuming they will be deleted, then I guess they are not needed?

You state:
Yeah, seems like inputAttributes just don't get populated, so this like has everything but the inputAttributes. Probably the assumption is it will get repopulated. The inputAttributes are deleted from database and repopulated from the screen...whatever the user types and then reinserted. But, even without deleting them, code just doesn't populate the inputAttributes.
Well if the inputAttributes are NOT populated there is nothing to compare to and therefore should be added as new inputAttributes, which you stated in a earlier post.

You state in your last post, "I'll step thru it again but looks like there's no Id. I'll post back.", well then all will be new inputAttributes.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40274346
They can't be new attributes because I'm not adding anything to the form. I bring up the page and just click save. I'll tace again to see why the Ids are zero. Maybe code is not populating the ids...and just populating value and name fields.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40274499
Well then they are not being stored where you think they should be or they are being deleted somewhere else.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40274591
I think the developers here have made the code very complicated. Maybe I think that because I'm new to it but doubt it because they're having lots of performance issues.

You looked at it some. You think it's over complicated? is it a decent code?
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40274619
I have not sat down to really understand the complete application and so I really can not comment on that. But from the small part that I have looked at it seems to violate the SOLID principle and the most likely reason why it is difficult to implement this change.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40274632
Thanks. It's been difficult, yes. Other sections also sound like they're difficult to modify...from what other developers say in scrums. But it's ok. I'm learning a lot and this is a good opportunity for me.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40274849
Ok, I know why there isn't any Ids. There's one ID but I doubt it can be used.

1. I don't delete the rows. I get all the rows upfront. Please see attached screenshot. There's an "id" column and an "inputid" column.

2. I bring up the page. I don't make any changes. I click save.

3. Look at "Ids" screenshot. I have all the inputAttributes but because the code assumes all the rows will be deleted and these are all new rows...the "ids" are not populated. The "inputIds" are there because it's a required field. The "Id" is an identity field in the database and gets recreated when all the rows are reinserted.

I doubt "inputId" can be used as a unique identifier. Maybe this is just not doable with the current code design.
database.png
Ids.png
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40275308
I do not know what else to have you do to get this issue resolve, not knowing the algorithm of the application it is difficult to know what is going on.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40275312
Thanks, Fernando. You've helped more than enough. It is what it is. It's just a slow design and I hear more and more about the website being slow.

Thanks so much for all your help. I learned a lot.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40276706
Thanks, Camilla. I wish I could have been more help. Good luck and have a great day.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40276712
You were more than a fantastic help. I wish I could repay you with something else besides 500 points. Cookies, maybe:)
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40276717
Thank you.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40278164
The Request for Attention was opened because there appeared to be a possibility that this issue might be resolved outside of EE's pages; we cannot award any points if that happens, because the information exchanged is not shared with the other participants -- and that's not fair.

What do you mean by "outside EE's pages"? And, I didn't open the request for attention. But it's ok.
0
 
LVL 64

Expert Comment

by:Fernando Soto
ID: 40280380
Hi Netminder;

Skype was not used although it was suggested it was not used.
0
 
LVL 7

Author Comment

by:Camillia
ID: 40280446
Netminder, as Fernando said, we didn't use Skype. I have a better understanding of the rules now. Thanks
0
 
LVL 7

Author Comment

by:Camillia
ID: 40280457
We know you didn't open the request. Someone who felt that another Expert might be getting an unfair advantage (again, the use of Skype) requested it. My post is a reminder to Experts to not suggest going "off-site" to help solve a problem, except as an absolute last resort.

I'm disappointed in whomever opened the "attention request" because "another Expert might be getting an unfair advantage". The main goal of EE should be creating a community to help each other and not who's getting the points and moving up the ranks. It's immature to open a request because someone thought another Expert is getting an unfair advantage. After all, we're not in high school anymore :)
0
 
LVL 7

Author Comment

by:Camillia
ID: 40281213
Thanks, Netminder, for your clarification.
0

Featured Post

How to Use the Help Bell

Need to boost the visibility of your question for solutions? Use the Experts Exchange Help Bell to confirm priority levels and contact subject-matter experts for question attention.  Check out this how-to article for more information.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article aims to explain the working of CircularLogArchiver. This tool was designed to solve the buildup of log file in cases where systems do not support circular logging or where circular logging is not enabled
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Loops Section Overview
Suggested Courses
Course of the Month14 days, 19 hours left to enroll

840 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question